Study Region

ggplot() +
  geom_sf(data=srme) +
  geom_sf(data=wrnf, fill="darkgrey") +
  geom_sf(data=blocks, fill=NA, lwd=0.4) +
  coord_sf(crs="EPSG:32613") +
  theme_light(11)

Spectral Response

Load the Sentinel-2 annual time-series (2019).


# Spectral Response
ts <- read_csv('../../data/tabular/mod/results/s2msi_l2a_aspen_TSy2019.csv', 
               show_col_types = FALSE) %>% 
  select(-c(id,.geo,TCB,TCG,TCW)) %>%
  rename(NDVI705 = NDRE)

head(ts)
NA

Tidy the time-series data.

  1. Remove cloud-contaminated pixels,
  2. Pivot the table to gather bands,
  3. Generate mean/median/stdev of reflectance by image date,
  4. Calculate weekly summaries

# Filter out cloud-contaminated pixels based on the Cloud Score + values
# Reference:  https://medium.com/google-earth/all-clear-with-cloud-score-bd6ee2e2235e

tsp <- ts %>%
  filter(
    cs >= 0.8, # >= Cloud Score + contamination
  ) %>%
  # add the month as a column
  mutate(month = month(image_date, label=TRUE, abbr=TRUE)) %>%
  # Filter null values
  filter(complete.cases(.))

# Test using some visualizations

# NDVI705 scatter plot with trend line
tsp %>%
  group_by(image_date) %>%
  summarize(band = median(NDVI705)) %>%
  ggplot(aes(x=image_date,y=band)) +
  geom_point(size=0.8) +
  geom_smooth(method="loess") +
  labs(x="Image Date", y="NDRE") +
  theme_bw(11)


# NDVI705 box plot
ggplot(data=tsp, aes(x=NDVI705, y=month)) +
  geom_boxplot() +
  coord_flip() +
  labs(y="Image Date", x="NDRE") +
  theme_bw(11)


rm(ts)

Now pivot the table longer:


tsp_ <- tsp %>%
  select(c(starts_with("B"),image_date,
           SLAVI,NDVI705,NDMI,ChlRE,IRECI,MCARI)) %>%
  group_by(image_date) %>%
  summarize_all(list(median)) %>%
  pivot_longer(
    cols = -c(image_date),
    names_to="band",
    values_to="reflectance"
  ) %>%
  # add a month and week label
  mutate(
    month = month(image_date, label=TRUE, abbr=TRUE),
    week = isoweek(image_date),
    biweek = cut.Date(image_date, breaks="2 week", labels=FALSE)
  )

head(tsp_, n=19)

rm(tsp)
gc()
           used  (Mb) gc trigger   (Mb) limit (Mb)  max used   (Mb)
Ncells  6004919 320.7   13778540  735.9         NA   8988140  480.1
Vcells 47615836 363.3  145620122 1111.0      16384 182020047 1388.8

Breakpoint analysis to identify significant shift in spectral signature throughout the year based on NDRE:


# Isolate one of the vegetation indices (Normalized Difference Red-edge Index)
df <- tsp_ %>% 
  filter(band == "NDVI705") %>% 
  select(image_date,week,reflectance) %>%
  group_by(image_date,week) %>%
  summarize(reflectance = median(reflectance)) %>%
  ungroup()
`summarise()` has grouped output by 'image_date'. You can override using the `.groups` argument.
# Create a weekly time-series object
df.ts <- ts(df$reflectance, frequency = 52)

# Implement the changepoint analysis based on NDRE
result <- changepoint::cpt.meanvar(df.ts, method="PELT")
bp.ind <- changepoint::cpts(result)

# Map breakpoints to dates
print("Breakpoint weeks:")
[1] "Breakpoint weeks:"
bp.dates <- df$week[bp.ind]
bp.dates
[1] 11 17 21 26 43 47
# Plot the result
plot(result)


rm(df, df.ts, result, bp.ind, bp.dates)

Perform the analysis by spatial block grid to get an idea of the variation in time-series breakpoints.

Plot of median daily observations:


# Reorder band factors
tsp_$band <- factor(tsp_$band, 
                   levels = c("B2","B3","B4","B5","B6","B7","B8","B8A","B11","B12",
                              "ChlRE","IRECI","NDMI","NDVI705","SLAVI","MCARI"))

tsp_ %>%
  filter(str_detect(band,"B")) %>%
  ggplot(aes(x=image_date,y=reflectance ,color=factor(band))) +
  geom_line(linewidth=0.4) +
  scale_color_viridis_d(option="turbo") +
  labs(x="Acquisition Date",y="Reflectance",color="S2 MSI Band") +
  theme_bw(14) +
  theme(axis.title.y = element_text(size=11,face="italic",
                                    margin = unit(c(0,0.4,0,0), "cm")),
        axis.title.x = element_text(size=11,face="italic",
                                    margin = unit(c(0.4,0,0,0), "cm")),
        axis.text = element_text(size=10),
        legend.text = element_text(size=10),
        legend.title = element_text(size=11))

Figure 3: Spectral Response of Aspen Forest Presence Data

Gather the seasonal composite start and end dates:

# Summer
(start_summer <- week(as.Date("2019-05-25")))
[1] 21
(end_summer <- week(as.Date("2019-08-13")))
[1] 33
# Autumn
(start_autumn <- week(as.Date("2019-09-02")))
[1] 35
(end_autumn <- week(as.Date("2019-11-14")))
[1] 46

Generate the weekly median reflectance.


# List for band colors

band_colors <- c(
  "B2"  = "#1E90FF",  # Blue: Dodger Blue
  "B3"  = "#00FF7F",  # Green: Spring Green
  "B4"  = "#FF0000",  # Red: Pure Red
  "B5"  = "#FFA07A",  # Red Edge: Light Salmon
  "B6"  = "#FF8C00",  # NIR: Dark Orange
  "B7"  = "#D2691E",  # NIR: Chocolate
  "B8"  = "#FFD700",  # NIR: Gold
  "B8A" = "#B8860B",  # NIR: Dark Goldenrod
  "B11" = "#008B8B",  # SWIR: Dark Cyan (dark shade of teal)
  "B12" = "#800080"   # SWIR: Purple
)


# Group by week, calculate the mean, plot

f3a <- tsp_ %>%
  filter(str_detect(band,"B")) %>%
  group_by(band,week) %>%
  summarize(refl = mean(reflectance)) %>%
  ggplot(aes(x=week,y=refl,color=factor(band))) +
  geom_line(linewidth=0.6) +
  scale_color_manual(values = band_colors) +
  labs(x="Acquisition Week",y="Reflectance",color=NULL) +
  theme_bw(11) +
  theme(axis.title.y = element_text(size=11,face="italic",
                                    margin = unit(c(0,0.4,0,0), "cm")),
        axis.title.x = element_text(size=11,face="italic",
                                    margin = unit(c(0.4,0,0,0), "cm")),
        axis.text = element_text(size=10),
        legend.text = element_text(size=8),
        legend.title = element_text(size=8),
        legend.position = c(0.2,0.95),
        legend.direction = "horizontal",
        legend.background = element_rect(fill="white", color="black"),
        plot.margin = unit(c(1.2,0.5,1.2,0.5), 'lines'),
        text = element_text(family = "Arial")) +
  annotate('rect', xmin=start_summer, xmax=end_summer, ymin=0, ymax=6000, alpha=.3, fill='gray') +
  annotate('rect', xmin=start_autumn, xmax=end_autumn, ymin=0, ymax=6000, alpha=.3, fill='gray') +
  geom_vline(xintercept=start_summer, linetype="dotted", lwd=0.8) +
  geom_vline(xintercept=end_summer, linetype="dotted", lwd=0.8) +
  geom_vline(xintercept=start_autumn, linetype="dotted", lwd=0.8) +
  geom_vline(xintercept=end_autumn, linetype="dotted", lwd=0.8)

f3a

Figure 1B: Time-series charts of the spectral indices:


# All bands

# Calculate the maximum reflectance for each band
max_refl_band <- tsp_ %>%
  group_by(band) %>%
  summarize(max_refl = max(reflectance))

# Now create your plot
tsp_ %>%
  group_by(band, week) %>%
  summarize(refl = mean(reflectance), .groups = 'drop') %>%
  ggplot(aes(x = week, y = refl, group = 1)) +
  geom_line() +
  geom_rect(data = max_refl_band, aes(xmin=start_summer, xmax=end_summer, ymin=0, ymax=max_refl), alpha=.3, fill='gray',
            inherit.aes = FALSE) +
  geom_rect(data = max_refl_band, aes(xmin=start_autumn, xmax=end_autumn, ymin=0, ymax=max_refl), alpha=.3, fill='gray',
            inherit.aes = FALSE) +
  geom_vline(xintercept = start_summer, linetype = "dotted", lwd = 0.8) +
  geom_vline(xintercept = end_summer, linetype = "dotted", lwd = 0.8) +
  geom_vline(xintercept = start_autumn, linetype = "dotted", lwd = 0.8) +
  geom_vline(xintercept = end_autumn, linetype = "dotted", lwd = 0.8) +
  facet_wrap(. ~ band, scales = "free_y") +
  theme_minimal() +
  theme(axis.title.y = element_text(size = 8, face = "italic",
                                    margin = unit(c(0, 0.4, 0, 0), "cm")),
        axis.title.x = element_text(size = 8, face = "italic",
                                    margin = unit(c(0.4, 0, 0, 0), "cm")),
        axis.text = element_text(size = 7),
        strip.text.x = element_text(size = 8))


# Spectral indices

max_refl_band_vi <- max_refl_band %>%
  filter(!str_detect(band,"B"))

f3b <- tsp_ %>%
  filter(!str_detect(band,"B")) %>%
  group_by(band,week) %>%
  summarize(refl = mean(reflectance)) %>%
  ggplot(aes(x=week, y=refl, group=1)) +
  geom_line(linewidth=0.6) +
  labs(x="Acquisition Week",y="") +
  geom_rect(data = max_refl_band_vi, aes(xmin = start_summer, xmax = end_summer, ymin = 0, ymax = max_refl), alpha = .3, fill = 'gray',
            inherit.aes = FALSE) +
  geom_rect(data = max_refl_band_vi, aes(xmin = start_autumn, xmax = end_autumn, ymin = 0, ymax = max_refl), alpha = .3, fill = 'gray',
            inherit.aes = FALSE) +
  geom_vline(xintercept=start_summer, linetype="dotted", lwd=0.8) +
  geom_vline(xintercept=end_summer, linetype="dotted", lwd=0.8) +
  geom_vline(xintercept=start_autumn, linetype="dotted", lwd=0.8) +
  geom_vline(xintercept=end_autumn, linetype="dotted", lwd=0.8) +
  facet_wrap(. ~ band, scales = "free") +
  theme_light(11) +
  theme(axis.title.y = element_text(size=11,face="italic",
                                    margin = unit(c(0,0.4,0,0), "cm")),
        axis.title.x = element_text(size=11,face="italic",
                                    margin = unit(c(0.4,0,0,0), "cm")),
        axis.text = element_text(size=10),
        strip.text.x = element_text(size = 11),
        plot.margin = unit(c(1.2,0.5,1.2,0.5), 'lines'),
        text = element_text(family = "Arial"))
f3b


rm(max_refl_band,max_refl_band_vi)

Figure 3C: Boxplot of seasonal composites:


cols <- c(
  "Summer" = "#87CEEB", 
  "Autumn" = "#DAA520"
)

seasonal <- tsp_ %>%
  mutate(season = if_else(week>=22&week<=37,"Summer","na"),
         season = if_else(week>=37&week<=48,"Autumn",season)) %>%
  filter(season!="na")

seasonal$season <- factor(seasonal$season, levels = c("Summer","Autumn"))
  
f3c <- seasonal %>%
  filter(str_detect(band,"B")) %>%
  ggplot(aes(x=band,y=reflectance,fill=season)) +
  geom_boxplot(outlier.size = 0.5, outlier.color = "grey30") +
  scale_fill_manual(values = cols) +
  theme_bw(12) +
  labs(x="Sentinel-2 MSI Band",y="Reflectance",fill="Season: ") +
  theme(axis.title.y = element_text(size=11,face="italic",
                                    margin = unit(c(0,0.4,0,0), "cm")),
        axis.title.x = element_text(size=11,face="italic",
                                    margin = unit(c(0.4,0,0,0), "cm")),
        axis.text = element_text(size=10),
        legend.position = "none",
        plot.margin = unit(c(1.2,0.5,1.2,0.5), 'lines'),
        text = element_text(family = "Arial"))
f3c

Figure 1D: Seasonal differences in the spectral indices


f3d <- seasonal %>%
  filter(!str_detect(band,"B")) %>%
  ggplot(aes(y=reflectance)) +
  geom_boxplot(aes(fill=season)) +
  scale_fill_manual(values = cols) +
  facet_wrap(. ~ band, scales = "free") +
  theme_bw(12) +
  labs(fill="Season",y="") +
  theme(axis.text.x = element_blank(),
        axis.ticks.x = element_blank(),
        axis.title.y = element_text(size=11,face="italic",
                                    margin = unit(c(0,0.4,0,0), "cm")),
        axis.title.x = element_text(size=11,face="italic",
                                    margin = unit(c(0.4,0,0,0), "cm")),
        axis.text = element_text(size=10),
        strip.text.x = element_text(size = 11),
        # legend.position = c(0.85,0.2),
        legend.position = "bottom",
        legend.title = element_text(size=12),
        legend.text = element_text(size=12),
        legend.spacing.y = unit(1.5, "cm"),
        legend.spacing.x = unit(0.5, 'cm'),
        plot.margin = unit(c(1.2,0.5,1.2,0.5), 'lines'),
        text = element_text(family = "Arial"))
f3d

Combine the plots:


# Arrange in a multi-panel plot

f3 <- ggarrange(f3a,f3b,f3c,f3d, nrow=2,ncol=2, widths=c(1, 0.75), labels = c("A", "B", "C", "D"))
Error in ggarrange(f3a, f3b, f3c, f3d, nrow = 2, ncol = 2, widths = c(1,  : 
  object 'f3a' not found

Version 2:


f3_ <- ggarrange(f3a,ggarrange(f3c,f3d,nrow=1,ncol=2),
                  ncol=1,widths=c(1, 0.75),
                  labels = c("A", "B", "C"))
Warning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font database
f3_


ggsave(f3_, file = "../../figures/Figure3_SpectralResponse_v2.png",
       dpi = 300, bg="white") # adjust dpi accordingly
Saving 14 x 8 in image

Clean up the time-series / spectral response data:

rm(f3,f3a,f3b,f3c,f3d,seasonal,band_colors,cols,end_autumn,end_summer,start_autumn,start_summer,tsp_)
gc()
          used  (Mb) gc trigger  (Mb) limit (Mb)  max used   (Mb)
Ncells 3530698 188.6   10685571 570.7         NA  10685571  570.7
Vcells 9107527  69.5  115377707 880.3      16384 180274361 1375.4

Model Selection and Accuracy Assessment

Load the accuracy assessment results for the best performing model (the final model in GEE). This represents the most parsimonious model (multicollinear bands removed and feature selection in rfUtilities). See the “accmeas.R” script.

accmeas <- read_csv('../../data/tabular/mod/results/best_model/southern_rockies_accmeas.csv',
                    show_col_types = FALSE)
opt_thresh <- read_csv('../../data/tabular/mod/results/best_model/southern_rockies_opt_thresh.csv',
                    show_col_types = FALSE)

glimpse(accmeas)
Rows: 1,000
Columns: 16
$ model     <dbl> 995, 995, 995, 995, 995, 995, 995, 995, 995, 995, 995, 995, 995, 995, 995, 995, 995, 995, 995, 995…
$ prSize    <dbl> 5702, 5702, 5702, 5702, 5702, 5702, 5702, 5702, 5702, 5702, 5702, 5702, 5702, 5702, 5702, 5702, 57…
$ bgSize    <dbl> 31332, 31332, 31332, 31332, 31332, 31332, 31332, 31332, 31332, 31332, 31332, 31332, 31332, 31332, …
$ cutoff    <dbl> 0.00000000, 0.01010101, 0.02020202, 0.03030303, 0.04040404, 0.05050505, 0.06060606, 0.07070707, 0.…
$ tp        <dbl> 5702, 5699, 5693, 5678, 5655, 5636, 5628, 5623, 5615, 5607, 5597, 5586, 5579, 5567, 5555, 5545, 55…
$ tpr       <dbl> 1.0000000, 0.9994739, 0.9984216, 0.9957910, 0.9917573, 0.9884251, 0.9870221, 0.9861452, 0.9847422,…
$ fn        <dbl> 0, 3, 9, 24, 47, 66, 74, 79, 87, 95, 105, 116, 123, 135, 147, 157, 165, 177, 182, 197, 203, 217, 2…
$ tn        <dbl> 0, 18718, 21721, 23814, 25884, 27044, 27731, 28193, 28521, 28794, 29045, 29269, 29450, 29600, 2972…
$ tnr       <dbl> 0.0000000, 0.5974084, 0.6932529, 0.7600536, 0.8261203, 0.8631431, 0.8850696, 0.8998149, 0.9102834,…
$ fp        <dbl> 31332, 12614, 9611, 7518, 5448, 4288, 3601, 3139, 2811, 2538, 2287, 2063, 1882, 1732, 1605, 1478, …
$ fpr       <dbl> 1.00000000, 0.40259160, 0.30674710, 0.23994638, 0.17387974, 0.13685689, 0.11493042, 0.10018511, 0.…
$ accuracy  <dbl> 0.1539666, 0.6593131, 0.7402387, 0.7963493, 0.8516228, 0.8824324, 0.9007669, 0.9131069, 0.9217476,…
$ precision <dbl> 0.1539666, 0.3111997, 0.3719942, 0.4302819, 0.5093218, 0.5679162, 0.6098169, 0.6417485, 0.6663897,…
$ recall    <dbl> 1.0000000, 0.9994739, 0.9984216, 0.9957910, 0.9917573, 0.9884251, 0.9870221, 0.9861452, 0.9847422,…
$ f1        <dbl> 0.2668476, 0.4746200, 0.5420356, 0.6009101, 0.6730140, 0.7213618, 0.7538678, 0.7775166, 0.7948754,…
$ mcc       <dbl> NA, 0.4308758, 0.5069640, 0.5696185, 0.6442674, 0.6939353, 0.7276642, 0.7523881, 0.7705284, 0.7862…
head(opt_thresh)
NA

Figure 3: Classification Scenarios


scenarios <- read_csv("../../data/tabular/mod/results/scenarios/rf_accmets_scenarios.csv", show_col_types = FALSE)

# Read in the best performing model results to append
final_model <- opt_thresh %>%
  mutate(Feature_Set = "Final_Model") %>%
  rename(F1 = f1) %>%
  select(Feature_Set,F1)

scenarios <- bind_rows(scenarios,final_model)
head(scenarios)

# Box plot version
ggplot(data=scenarios, aes(x=reorder(Feature_Set,F1),y=F1)) +
  geom_boxplot(outlier.size=0.6, fill="lightblue") +
  ylim(0,1) +
  labs(x="Classification Scenario",y="F1-score", tag="A") +
  theme_bw(10) +
  theme(
        plot.margin = unit(c(0.2,0.2,0.2,0.2), 'lines'),
        axis.title.y = element_text(size=10,face="italic",
                                    margin = unit(c(0,0.4,0,0), "cm")),
        axis.title.x = element_text(size=10,face="italic",
                                    margin = unit(c(0.4,0,0,0), "cm")),
        axis.text = element_text(size=9),
        strip.text.x = element_text(size = 10),
        axis.text.x = element_text(angle = 45, hjust=1),
        text = element_text(family = "Arial"))


# Calculating the means and standard errors for F1-scores
# Plot as a bar chart with standard errors
f3a <- scenarios %>%
  group_by(Feature_Set) %>%
  summarise(
    Mean = mean(F1),
    SE = sd(F1) / sqrt(n())
  ) %>%
  # Plotting the bar chart with error bars
  ggplot(aes(x=reorder(Feature_Set, Mean), y=Mean)) +
    geom_bar(stat="identity", fill="lightblue", width=0.75) +
    geom_errorbar(aes(ymin=Mean-SE, ymax=Mean+SE), width=0.2) +
    ylim(0, 1) +
    labs(x="Classification Scenario", y="F1-score") +
    theme_bw(base_size = 10) +
    theme(
      plot.margin = unit(c(0.2,0.2,0.2,0.2), 'lines'),
      axis.title.y = element_text(size=10, face="italic", margin = unit(c(0,0.4,0,0), "cm")),
      axis.title.x = element_text(size=10, face="italic", margin = unit(c(0.4,0,0,0), "cm")),
      axis.text = element_text(size=9),
      strip.text.x = element_text(size = 10),
      axis.text.x = element_text(angle = 45, hjust=1),
      text = element_text(family = "Arial")
    )
f3a


# Save out
ggsave(f3a, file = "../../figures/Figure4A_Class_Scenarios.png",
       dpi = 300, bg="white") # adjust dpi accordingly

Table X. Average F1-score and other metrics for the classification scenarios and the final model

# Read in the best performing model results to append
final_model <- opt_thresh %>%
  mutate(Feature_Set = "Final_Model") %>%
  rename(F1 = f1) %>%
  select(Feature_Set,F1)

# Add the final model to the data frame
scenarios_ <- bind_rows(scenarios,final_model)

# Create the final table
df <- scenarios_ %>%
  group_by(Feature_Set) %>%
  summarize(
    F1_mn = mean(F1),
    F1_sd = sd(F1)
  ) %>%
  mutate(
    n_features = if_else(Feature_Set == "Summer_S1" | Feature_Set == "Winter_S1", 5, 0),
    n_features = if_else(Feature_Set == "Summer_S1_GLCM" | Feature_Set == "Winter_S1_GLCM", 13, n_features),
    n_features = if_else(Feature_Set == "Summer_Winter_S1", 23, n_features),
    n_features = if_else(Feature_Set == "Summer_S2" | Feature_Set == "Autumn_S2", 13, n_features),
    n_features = if_else(Feature_Set == "Summer_S2_VI" | Feature_Set == "Autumn_S2_VI", 19, n_features),
    n_features = if_else(Feature_Set == "Summer_Autumn_S2", 35, n_features),
    n_features = if_else(Feature_Set == "Combined_S1_S2", 55, n_features),
    n_features = if_else(Feature_Set == "Final_Model", 17, n_features)
  )

# Create a tidy table

# Set font name for table
fontname <- "Times New Roman"

# Clean up extra rows and digits
cleaned <- df %>%
 mutate_if(is.double, ~ round(., digits = 4)) %>%
 # Sort from fastest to slowest
 arrange(desc(F1_mn))

# Fix names and add units
names(cleaned) <- c("Feature Set","Mean F1-score","Standard Deviation","Number of Features")

# Create the flextable
ft <- flextable::flextable(cleaned) %>%
  flextable::font(fontname = fontname, part = "all") %>%
  flextable::autofit() %>% 
  flextable::fit_to_width(7.5)
ft

Feature Set

Mean F1-score

Standard Deviation

Number of Features

Final_Model

0.9321

0.0080

17

Combined_S1_S2

0.9143

0.0118

55

Summer_Autumn_S2

0.9091

0.0122

35

Summer_S2_VI

0.8768

0.0155

19

Summer_S2

0.8725

0.0150

13

Autumn_S2_VI

0.8416

0.0175

19

Autumn_S2

0.8326

0.0185

13

Summer_Winter_S1

0.6231

0.0303

23

Winter_S1_GLCM

0.5930

0.0372

13

Summer_S1

0.5589

0.0241

5

Summer_S1_GLCM

0.5472

0.0203

13

Winter_S1

0.4629

0.0274

5

# print(ft, preview = "docx")

rm(scenarios_,df,ft,fontname,cleaned)

Figure S2. Model Performance Metrics; AUC-PR, Optimum Cutoff

This will go into the supplement.

The optimum cutoff value for classification is defined as the average cutoff across folds at which the F1-score is maximized. We can see the optimum cutoff value and how it corresponds with F1-score and the AUC-PR curve.

paste0("Optimum cutoff for classification: ",round((opt_cutoff <- mean(opt_thresh$cutoff_f1)),3))
[1] "Optimum cutoff for classification: 0.42"
paste0("Average F1-score: ",round((f1max <- max(opt_thresh$f1)),3))
[1] "Average F1-score: 0.942"
# AUC-PR Curve (approximation)

fs2a <- ggplot(data=accmeas) +
  geom_line(aes(x=fpr,y=tpr,color=factor(model)),linewidth=0.4) +
  scale_color_viridis_d(option="turbo") +
  scale_x_continuous(limits=c(0,1)) +  # Set x axis limits from 0 to 1
  scale_y_continuous(limits=c(0,1)) +
  labs(x='False Positive Rate', y='True Positive Rate', tag="A") +
  coord_fixed(ratio = 1) +  # to keep the x and y axes scales same
  geom_abline(intercept = 0, slope = 1, linetype = "dashed", color = "black") +  # diagonal
  theme_light() +
  theme(legend.position = "none",
        axis.title.y = element_text(size=11,face="italic",
                                    margin = unit(c(0,0.4,0,0), "cm")),
        axis.title.x = element_text(size=11,face="italic",
                                    margin = unit(c(0.4,0,0,0), "cm")),
        axis.text = element_text(size=10),
        strip.text.x = element_text(size = 11))

fs2b <- ggplot(data=accmeas) +
  geom_line(aes(x=cutoff,y=f1,color=factor(model)),linewidth=0.4) +
  scale_color_viridis_d(option="turbo") +
  # geom_vline(xintercept=0.50, linetype="dashed", color="black") +
  geom_point(aes(x=opt_cutoff, y=f1max),
             color = "black", size = 3, shape = 19) +
  scale_x_continuous(limits=c(0,1)) +  # Set x axis limits from 0 to 1
  scale_y_continuous(limits=c(0,1)) +
  labs(x='Classification Threshold', y='F1 Score', tag="B") +
  geom_text(aes(x=opt_cutoff, y=f1max),
            label = paste0('Optimal threshold: ', round(opt_cutoff,3)),
            nudge_x = 0.0, nudge_y = 0.05, size = 3.5) +
  coord_fixed(ratio = 1) +  # to keep the x and y axes scales same
  theme_light() +
  theme(legend.position = "none",
        axis.title.y = element_text(size=11,face="italic",
                                    margin = unit(c(0,0.4,0,0), "cm")),
        axis.title.x = element_text(size=11,face="italic",
                                    margin = unit(c(0.4,0,0,0), "cm")),
        axis.text = element_text(size=10),
        strip.text.x = element_text(size = 11))

# Arrange
(fs2 <- ggarrange(fs2a,fs2b))


# Save out
ggsave(fs2, file = "../../figures/FigureS2_Model_Performance.png",
       dpi = 300, bg="white") # adjust dpi accordingly
Saving 6.5 x 3 in image
rm(fs2a,fs2b,fs2)

Using the optimum cutoff value, we can classify the test data and calculate the Precision, Recall, and F1-score.

testPart <- "../../data/tabular/mod/results/best_model/southern_rockies_test_probs_n55.csv"
# Assign the classification label based on the optimum cutoff
testData <- read_csv(testPart, show_col_types=FALSE) %>%
  rename(gee_id = `system:index`,
         TrueLabel = label) %>%
  dplyr::select(-.geo) %>%
  mutate(probability = probability*0.001, # scale back
         ClassLabel = if_else(probability >= opt_cutoff, 1, 0)) %>%
  group_by(seed) %>%
  summarize(
    tp = sum(TrueLabel == 1 & ClassLabel == 1),
    tn = sum(TrueLabel == 0 & ClassLabel == 0),
    fp = sum(TrueLabel == 0 & ClassLabel == 1),
    fn = sum(TrueLabel == 1 & ClassLabel == 0),
    accuracy = (tp + tn) / (tp + tn + fp + fn),
    precision = tp / (tp + fp),
    recall = tp / (tp + fn),
    f1 = 2 * (precision * recall) / (precision + recall),
    .groups = 'drop' # This ensures that the result is a single data frame, not a grouped one
  )
head(testData,10)

# First, get the overall F1/MCC for the classification
print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
[1] "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
paste0("Average F1-score for the best performing model at the optimum cutoff: ",mean(testData$f1))
[1] "Average F1-score for the best performing model at the optimum cutoff: 0.931081536861107"
paste0("Range of F1-scores: ",range(testData$f1))
[1] "Range of F1-scores: 0.915951638065523" "Range of F1-scores: 0.940687361419069"
paste0("StDev of F1-scores: ",sd(testData$f1))
[1] "StDev of F1-scores: 0.00849272592798287"
summary(testData$f1)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
 0.9160  0.9245  0.9323  0.9311  0.9384  0.9407 
# rm(testPart,testData)

Plot the model accuracy results for the optimum threshold.

Figure 4A: Model Accuracy (F1 and OA)


cols <- c("F1 Score"="#1f78b4","Overall Accuracy"="gray75")

testData <- testData %>%
  mutate(Fold = as.numeric(factor(seed, levels = sort(unique(seed)))))

f4b <- ggplot(data=testData) +
  geom_hline(yintercept=mean(testData$precision),linetype="dashed",color="gray85") +
  geom_hline(yintercept=mean(testData$recall),linetype="dashed",color="gray45") +
  geom_hline(yintercept=mean(testData$f1),linetype="dashed",color="#1f78b4") +
  # Plot the Precision and Recall
  geom_line(aes(x=factor(Fold),y=precision,group=1),color="gray65") +
  geom_line(aes(x=factor(Fold),y=recall,group=1),color="gray35") +
  # Plot the F1-score
  geom_line(aes(x=factor(Fold),y=f1,group=1),color="#1f78b4") +
  geom_point(aes(x=factor(Fold),y=f1,size=f1), color="#1f78b4") +
  scale_size(range = c(3,6)) +
  coord_cartesian(ylim = c(0.85, 1)) +
  labs(x="Fold", y="Score", tag="B") +
  # annotate("text", x = 2.6, y = 0.88, label = paste("Average F1 Score: ", round(mean(testData$f1), 2))) +
  theme_bw() +
  guides(size="none") +
  theme(
        plot.margin = unit(c(0.2,0.2,0.2,0.2), 'lines'),
        axis.title.y = element_text(size=11,face="italic",
                                    margin = unit(c(0,0.4,0,0), "cm")),
        axis.title.x = element_text(size=11,face="italic",
                                    margin = unit(c(0.4,0,0,0), "cm")),
        axis.text = element_text(size=10),
        strip.text.x = element_text(size = 11),
        text = element_text(family = "Arial"))
f4b


ggsave(f4b, file = "../../figures/Figure4A_Best_Model_Acc_F1.png",
       dpi = 300, bg="white") # adjust dpi accordingly
arr4 <- ggarrange(f4a,ggarrange(f4b,f5b, ncol=2, nrow=1, align='h'), nrow=2, align="h")
Warning: Removed 30 rows containing missing values (`geom_line()`).Warning: Removed 1000 rows containing missing values (`geom_text()`).
arr4

ggsave(arr4, file = "../../figures/Figure4_ModelSel_BestModel_ThreshOpt.png",
       dpi = 300, bg="white") # adjust dpi accordingly
Saving 7.5 x 7.5 in image

Figure 6: Feature importance from the best model

# Feature Importance
ftr_imp <- read_csv('../../data/tabular/mod/results/best_model/southern_rockies_feature_imps_n55.csv',
                    show_col_types = FALSE)
glimpse(ftr_imp)
Rows: 10
Columns: 20
$ `system:index` <dbl> 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
$ B2_autumn      <dbl> 49.49731, 53.55798, 46.15995, 44.59732, 36.44691, 52.99102, 49.53374, 53.95200, 50.03963, 45.…
$ B2_summer      <dbl> 34.97310, 32.51703, 37.61583, 39.28662, 31.10338, 33.16864, 30.08473, 33.45479, 37.49951, 37.…
$ B3_autumn      <dbl> 165.1867, 157.1493, 168.6397, 162.4648, 149.4557, 168.8254, 161.2280, 162.8126, 170.3982, 157…
$ B4_autumn      <dbl> 89.04790, 102.29013, 99.24388, 96.15822, 88.75827, 93.63894, 89.72539, 104.08191, 98.86185, 1…
$ B4_summer      <dbl> 72.03866, 84.54776, 67.78854, 75.62744, 65.89027, 80.50073, 75.79995, 77.82927, 76.71519, 77.…
$ B5_summer      <dbl> 58.24769, 64.08761, 58.65716, 70.13235, 49.87718, 68.41857, 61.11606, 62.80698, 63.17702, 56.…
$ CIRE_autumn    <dbl> 57.83576, 58.81169, 64.52829, 52.95184, 53.56839, 61.44669, 50.77975, 62.17084, 63.96603, 61.…
$ CIRE_summer    <dbl> 188.3868, 190.1987, 159.6398, 185.3497, 194.2375, 198.4452, 181.9208, 211.1488, 163.8799, 195…
$ MNDWI_autumn   <dbl> 356.2320, 337.3275, 330.8980, 316.3036, 316.3465, 353.9160, 324.2431, 353.9166, 337.8683, 316…
$ MNDWI_summer   <dbl> 42.57642, 40.28980, 43.35349, 38.25414, 38.65055, 49.04463, 37.95904, 42.87194, 41.04543, 39.…
$ SLAVI_autumn   <dbl> 29.03284, 31.82708, 35.25218, 30.28867, 26.53197, 35.65043, 25.52154, 31.37167, 30.51408, 30.…
$ SLAVI_summer   <dbl> 151.3940, 149.6327, 132.2234, 135.1090, 159.6722, 168.1524, 162.8332, 171.8494, 144.0258, 151…
$ VH_summer_ent  <dbl> 4.398385, 5.996275, 4.397207, 3.379485, 5.188208, 5.741732, 6.728794, 6.930599, 7.557105, 6.2…
$ VH_winter_ent  <dbl> 4.592535, 6.320210, 5.227573, 5.173139, 5.419760, 6.514255, 8.699771, 10.378239, 6.127086, 7.…
$ VV_summer      <dbl> 127.0105, 159.4653, 147.7406, 142.7680, 142.0232, 180.8220, 149.5889, 176.8713, 160.5565, 161…
$ VV_winter      <dbl> 53.30679, 51.75154, 54.96236, 58.44277, 55.25150, 67.25097, 56.83036, 62.59225, 57.43706, 65.…
$ elevation      <dbl> 282.0402, 276.5765, 268.8157, 251.2940, 272.3188, 301.4409, 266.4146, 280.5173, 305.6660, 272…
$ seed           <dbl> 995, 822, 523, 102, 121, 433, 590, 151, 858, 506
$ .geo           <chr> "{\"type\":\"MultiPoint\",\"coordinates\":[]}", "{\"type\":\"MultiPoint\",\"coordinates\":[]}…

# Tidy the data frame
df.imp <- ftr_imp %>% 
  select(-c(.geo,`system:index`)) %>%
  rename(model = seed) %>%
  mutate(model = as.factor(model))

# Pivot longer
df.imp.p <- df.imp %>%
  pivot_longer(cols = -model) %>%
  rename(importance = value,
         band = name) %>%
  mutate(season = if_else(str_detect(band,"_autumn"), "Autumn S2", "Summer S2"),
         season = if_else(str_detect(band,"_winter"), "Winter S1", season),
         season = if_else(str_detect(band,"VV_summer"), "Summer S1", season),
         season = if_else(str_detect(band,"VH_summer"), "Summer S1", season))
glimpse(df.imp.p)
Rows: 170
Columns: 4
$ model      <fct> 995, 995, 995, 995, 995, 995, 995, 995, 995, 995, 995, 995, 995, 995, 995, 995, 995, 822, 822, 82…
$ band       <chr> "B2_autumn", "B2_summer", "B3_autumn", "B4_autumn", "B4_summer", "B5_summer", "CIRE_autumn", "CIR…
$ importance <dbl> 49.497311, 34.973098, 165.186734, 89.047904, 72.038658, 58.247693, 57.835765, 188.386846, 356.232…
$ season     <chr> "Autumn S2", "Summer S2", "Autumn S2", "Autumn S2", "Summer S2", "Summer S2", "Autumn S2", "Summe…
# Grab the top 20 over all model runs
top <- df.imp.p %>%
  group_by(band) %>%
  summarize(median = median(importance)) %>%
  ungroup()
top <- head(arrange(top,desc(median)), n = 10)

# Color palette

cols <- c(
  "Summer S2" = "#87CEEB", 
  "Autumn S2" = "#DAA520",
  "Winter S1" = "gray29",
  "Summer S1" = "gray89"
)

# Boxplot

# %>% filter(band %in% top$band)
f6 <- ggplot(data=df.imp.p %>% filter(band %in% top$band), 
              aes(x=reorder(band,importance), fill=season)) +
  geom_boxplot(aes(y=importance), position = position_dodge(0.5)) +
  scale_fill_manual(values = cols, 
                    labels = c("Autumn S2","Summer S1","Summer S2","Winter S1")) +
  coord_flip() +
  theme_bw(11) +
  theme(axis.text.x = element_text(size=11),
        axis.text.y = element_text(angle = 0, vjust=0, size=11),
        axis.title = element_text(size=11,face="italic"),
        legend.position = "bottom",
        legend.justification = c(1.2,0.5), # Left-aligns the legends
        legend.text = element_text(size=11),
        text = element_text(family = "Arial"),
        plot.margin=unit(c(0.5,0.5,0.5,0.5),"cm")) +
  labs(x="Feature", y="Importance", fill="Season: ")
f6


ggsave(f6, file = "../../figures/Figure6_FeatureImportance_top10.png",
       dpi=300, bg="white") # adjust dpi accordingly

Clean up!

rm(list = ls.str(mode = 'numeric'))
rm(accmeas,accmeas.best,accmeas.mn,accmeas.s,df.imp,
   df.imp.p,f4,f4a,f4b,f6,ftr_imp,top,cols,arr4,cleaned,
   df,f5b,final_model,ft,opt_thresh,scenarios,scenarios_,testData)
Warning: object 'accmeas.best' not foundWarning: object 'accmeas.mn' not foundWarning: object 'accmeas.s' not foundWarning: object 'f4' not foundWarning: object 'f4a' not foundWarning: object 'arr4' not foundWarning: object 'cleaned' not foundWarning: object 'df' not foundWarning: object 'f5b' not foundWarning: object 'ft' not foundWarning: object 'scenarios_' not found
gc()
           used  (Mb) gc trigger  (Mb) limit (Mb) max used  (Mb)
Ncells  3815449 203.8    5673305 303.0         NA  5673305 303.0
Vcells 43611970 332.8   82892856 632.5      16384 74919126 571.6

Spatial Agreement and Landscape Patch Dynamics

Table X: Accuracy of the reference datasets based on test data

ref_acc <- read_csv('../../data/tabular/mod/results/best_model/southern_rockies_ref_accmeas.csv',
                    show_col_types = F)
head(ref_acc)

# Create a tidy table
df <- ref_acc %>%
  filter(metric == "Predicted") %>%
  select(reference,precision, recall, f1_score)
head(df)

# Create a tidy table

# Set font name for table
fontname <- "Times New Roman"

# Clean up extra rows and digits
cleaned <- df %>%
 mutate_if(is.double, ~ round(., digits = 4)) %>%
 # Sort from fastest to slowest
 arrange(desc(f1_score))

# Fix names and add units
names(cleaned) <- c("Data Source","Precision","Recall","F1-score")

# Create the flextable
ft <- flextable::flextable(cleaned) %>%
  flextable::font(fontname = fontname, part = "all") %>%
  flextable::autofit() %>% 
  flextable::fit_to_width(7.5)
ft

Data Source

Precision

Recall

F1-score

TrueLabel

0.9516

0.9116

0.9311

treemap

0.8087

0.9302

0.8652

lfevt

0.8168

0.8995

0.8562

itsp

0.7959

0.8669

0.8299

print(ft, preview = "docx")

rm(df,ref_acc)

Figure 7: Spatial Agreement (pixel-based)

aggr.sr <- aggr.sr %>%
  mutate(source = if_else(source == "usfs_treemap16_balive_int_bin_srme_10m", "USFS TreeMap", source))
glimpse(aggr.sr)
Rows: 750
Columns: 10
$ block     <dbl> 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 2…
$ tp        <dbl> 288, 0, 1410, 16, 90669, 0, 368, 396, 44319, 62550, 62944, 68, 111143, 9013, 0, 0, 0, 0, 34474, 487164,…
$ fp        <dbl> 653, 2, 3864, 714, 477299, 225, 3639, 7111, 76792, 189814, 102800, 2669, 72030, 83822, 927, 231, 31, 66…
$ fn        <dbl> 4790, 468, 7239, 1118, 75636, 0, 16144, 6810, 106960, 225459, 126158, 2334, 677624, 45413, 9, 0, 0, 0, …
$ blocksize <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1…
$ precision <dbl> 0.30605739, 0.00000000, 0.26734926, 0.02191781, 0.15963751, 0.00000000, 0.09183928, 0.05275077, 0.36593…
$ recall    <dbl> 0.05671524, 0.00000000, 0.16302463, 0.01410935, 0.54519708, NA, 0.02228682, 0.05495420, 0.29296201, 0.2…
$ f1        <dbl> 0.09569696, NA, 0.20254256, 0.01716738, 0.24696264, NA, 0.03586919, 0.05382995, 0.32540842, 0.23150676,…
$ source    <chr> "LANDFIRE EVT", "LANDFIRE EVT", "LANDFIRE EVT", "LANDFIRE EVT", "LANDFIRE EVT", "LANDFIRE EVT", "LANDFI…
$ region    <chr> "Southern Rockies", "Southern Rockies", "Southern Rockies", "Southern Rockies", "Southern Rockies", "So…

Remove blocks with little to no aspen forest cover. First look at the distribution of aspen forest cover across spatial blocks.


# Plot the distribution of aspen area
blocks %>%
  st_set_geometry(NULL) %>%
  as_tibble() %>%
  mutate(s2aspen_sum = as.integer(s2aspen_sum)) %>%
  ggplot(aes(x=s2aspen_sum)) +
  scale_x_continuous(trans="log") +
  geom_histogram() +
  theme_minimal(12)


# Filter out non-aspen blocks
blocks_aspen <- blocks %>%
  filter(s2aspen_sum > 10)

flabs <- c(precision = "Precision", recall = "Recall", f1 = "F1-score")

# Reshape the agreement table for plotting facet wrap

# Southern Rockies (by block)
aggr.sr.m <- reshape2::melt(
  aggr.sr, id.vars = c("blocksize", "source", "region", "block"), 
  measure.vars = c("precision", "recall", "f1"), variable.name = "statistic"
) %>%
  na.omit() %>%
  filter(blocksize == 1,
         block %in% blocks_aspen$id)

# White River NF
aggr.wr.m <- reshape2::melt(
  aggr.wr, id.vars = c("blocksize", "source", "region"), 
  measure.vars = c("precision", "recall", "f1"), variable.name = "statistic"
) %>%
  na.omit() %>%
  filter(blocksize == 1)

# Facet wrap plot of the statistics by block group

f7 <- ggplot(data=aggr.sr.m, aes(x=factor(blocksize), y=value, fill=factor(source))) +
  geom_boxplot(position = position_dodge()) + 
  scale_fill_viridis_d(labels=c("LANDFIRE EVT","USFS TreeMap","USFS ITSP"), name="",
                       breaks=c("LANDFIRE EVT", "USFS TreeMap", "USFS ITSP")) +
  facet_wrap(~statistic, labeller = labeller(statistic = flabs)) +
  theme_bw(11) +
  theme(legend.position = "bottom",
        legend.box = "vertical",
        legend.justification = c(0.5,0.5), # Left-aligns the legends
        legend.spacing.y = unit(-5, "pt"), # Adjusts the gap between the legends
        # legend.text = ggtext::element_markdown(halign = 0), # This aligns each individual legend's text to the left
        axis.title.x=element_blank(),  # Remove x axis title
        axis.text.x=element_blank(),   # Remove x axis text
        axis.ticks.x=element_blank(),  # Remove x axis ticks
        axis.title = element_text(size=11,face="italic")) +
        # text = element_text(family = "Times New Roman")) +
  labs(x="Blocksize", y="Value")

# Add the WRNF statistics
f7 <- f7 +
  geom_point(data=aggr.wr.m, aes(x=factor(blocksize), y=value, group=factor(source)),
             position=position_dodge(0.75), shape=18, size=4, color="white") +
  # Then add the actual point in black
  geom_point(data=aggr.wr.m, aes(x=factor(blocksize), y=value, group=factor(source)),
             position=position_dodge(0.75), shape=18, size=3, color="black") +
  scale_shape_manual(name="Subregion", values=c("White River NF" = 8)) +
  guides(
    fill=guide_legend(override.aes=list(shape=NA)), # No shape in the fill legend
    shape=guide_legend(title=" ", override.aes=list(color="black")) # Shape legend for Subregion
  )
f7


# Save out
ggsave(f7, file = "../../figures/Figure7_Agreement_Box.png",
       dpi = 300, bg="white") # adjust dpi accordingly
# Add a spatial map of statistics by blocks
blocks_ <- blocks %>%
  mutate(id = as.numeric(id)-1) %>%
  rename(block = id) %>%
  left_join(aggr.sr, by="block") %>%
  pivot_longer(cols = c(precision, recall, f1), names_to = "statistic", values_to = "value") %>%
  na.omit()

f7b <- ggplot(data = blocks_) +
  geom_sf(aes(fill = value)) +
  geom_sf(data=srme, fill=NA, color="grey20", linewidth=0.4) +
  facet_grid(source ~ statistic) +
  scale_fill_viridis_c(option = "C") + # Use a continuous color scale
  theme_void() +
  theme(
    strip.text.x = element_text(size = 10),
    legend.position = "bottom"
  ) +
  guides(fill = guide_colourbar(direction = "horizontal", barwidth = 10, barheight = 0.60,
                                ticks=F, title.position = "left"),
         label.theme = element_text(angle = 0, size = 9)) +
  labs(fill="", tag="B")
f7b

Join the two plots.


f7 <- f7 + theme(plot.margin = unit(c(0.5, 10, 0.5, 0.5),"mm")) # Add right margin to the left plot
f7b <- f7b + theme(plot.margin = unit(c(0.5, 0.5, 10, 0.5),"mm")) # Add left margin to the right plot

arr7 <- ggarrange(f7, f7b, ncol=2, align="h", widths = c(1.5,1))
Warning: Graphs cannot be horizontally aligned unless the axis parameter is set. Placing graphs unaligned.
arr7

# Save out
ggsave(arr7, file = "../../figures/Figure7_Agreement_Box_Maps_Arr.png",
       dpi = 300, bg="white") # adjust dpi accordingly
Saving 7.29 x 4.51 in image


brewer.pal(n=5,"Set1")

cols <- c("#4DAF4A", "#984EA3", "#FF7F00")

glimpse(ref.global)

# # Get an average f1 score table
# f1mn <- ref.global %>%
#   group_by

# Add a statistics column for legend (could do this for F1 too ...)
ref.global.m <- reshape2::melt(
  ref.global, id.vars = c("blocksize", "source", "region"), 
  measure.vars = c("prec", "rec"), variable.name = "statistic"
)

glimpse(ref.global.m)

f7a <- ggplot(data=ref.global.m) +
  geom_line(aes(x=blocksize,y=value,color=factor(source), linetype=statistic), linewidth=0.6) +
  geom_point(data=ref.global, aes(x=blocksize, y=f1, color=source)) +
  scale_x_continuous(breaks=c(1,3,5,7,9)) +
  scale_linetype_manual(values = c("prec" = "dotted", "rec" = "solid"), name="Statistic: ",
                        labels=c("Precision","Recall")) +
  facet_wrap(~region, scales = "fixed") +
  scale_color_manual(values=cols, labels=c("LANDFIRE EVT","USFS TreeMap","USFS ITSP"), name="Source: ") +
  theme_light(14) +
  ylim(0,1) +
  theme(legend.position = "top",
        legend.box = "vertical",
        legend.justification = c(0.5,0.5), # Left-aligns the legends
        legend.spacing.y = unit(-10, "pt"), # Adjusts the gap between the legends
        legend.text = ggtext::element_markdown(halign = 0), # This aligns each individual legend's text to the left
        axis.title = element_text(size=12,face="italic"),
        text = element_text(family = "Arial")) +
  labs(x="Blocksize (pixels)",y="Value")
  
f7a

ggsave(f7a, file = "../../figures/Figure7_Global_PrecRec_Agreement.png", dpi=300)

Clean up!

rm(ref.global,ref.global.m,f7a,f7b,f7,ref.focal)
gc()

Figure 8: Landscape Patch Dynamics

class.sr_ <- class.sr %>%
  mutate(source = if_else(source=="lc16_evt_200_bin_srme_10m", "LANDFIRE EVT", source),
         source = if_else(source=="s2aspen_prob_10m_binOpt_srme", "Sentinel-based Map", source),
         source = if_else(source=="usfs_itsp_aspen_ba_gt10_srme_10m", "USFS ITSP", source),
         source = if_else(source=="usfs_treemap16_balive_int_bin_srme_10m", "USFS TreeMap", source))
patch.sr_ <- patch.sr %>%
  mutate(source = if_else(source=="lc16_evt_200_bin_srme_10m", "LANDFIRE EVT", source),
         source = if_else(source=="s2aspen_prob_10m_binOpt_srme", "Sentinel-based Map", source),
         source = if_else(source=="usfs_itsp_aspen_ba_gt10_srme_10m", "USFS ITSP", source),
         source = if_else(source=="usfs_treemap16_balive_int_bin_srme_10m", "USFS TreeMap", source),
         area_qt = factor(ntile(area, 4), labels = c("Q1", "Q2", "Q3", "Q4")))
glimpse(patch.sr_)
Rows: 4,023,649
Columns: 9
$ ...1                 <dbl> 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, …
$ area                 <dbl> 0.27, 0.18, 0.18, 0.18, 0.18, 0.09, 0.09, 0.09, 0.36, 0.27, 0.09, 0.18, 0.36, 0.18, 0.45, 2.07, 0.…
$ perimeter            <dbl> 240, 240, 240, 240, 240, 120, 120, 120, 360, 240, 120, 180, 360, 180, 360, 1440, 360, 180, 300, 18…
$ perimeter_area_ratio <dbl> 888.8889, 1333.3333, 1333.3333, 1333.3333, 1333.3333, 1333.3333, 1333.3333, 1333.3333, 1000.0000, …
$ shape_index          <dbl> 1.090909, 1.333333, 1.333333, 1.333333, 1.333333, 1.000000, 1.000000, 1.000000, 1.500000, 1.090909…
$ region               <chr> "srme", "srme", "srme", "srme", "srme", "srme", "srme", "srme", "srme", "srme", "srme", "srme", "s…
$ source               <chr> "LANDFIRE EVT", "LANDFIRE EVT", "LANDFIRE EVT", "LANDFIRE EVT", "LANDFIRE EVT", "LANDFIRE EVT", "L…
$ block_id             <dbl> 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,…
$ area_qt              <fct> Q3, Q3, Q3, Q3, Q3, Q2, Q2, Q2, Q4, Q3, Q2, Q3, Q4, Q3, Q4, Q4, Q3, Q3, Q3, Q3, Q3, Q2, Q3, Q2, Q3…
glimpse(class.sr_)
Rows: 339
Columns: 8
$ ...1       <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
$ total_area <dbl> 1663.05, 1512.79, 2880.09, 1891.05, 7887.67, 12885.39, 11173.34, 9641.01, 11779.70, 7447.66, 9000.44, 2711.0…
$ prop_area  <dbl> 4.400219, 3.822703, 7.054768, 4.771215, 17.424149, 24.233280, 21.774401, 19.439621, 22.823152, 15.820654, 18…
$ n_patch    <dbl> 2729, 2093, 4281, 2614, 9527, 11541, 11707, 8969, 10877, 7553, 15565, 6617, 6633, 4600, 3055, 5846, 3477, 40…
$ patch_den  <dbl> 7.220587, 5.288849, 10.486291, 6.595254, 21.045488, 21.704914, 22.814388, 18.084616, 21.074172, 16.044422, 3…
$ region     <chr> "srme", "srme", "srme", "srme", "srme", "srme", "srme", "srme", "srme", "srme", "srme", "srme", "srme", "srm…
$ source     <chr> "LANDFIRE EVT", "LANDFIRE EVT", "LANDFIRE EVT", "LANDFIRE EVT", "LANDFIRE EVT", "LANDFIRE EVT", "LANDFIRE EV…
$ block_id   <dbl> 4, 8, 9, 10, 12, 20, 21, 22, 23, 24, 26, 27, 28, 29, 30, 37, 38, 42, 43, 45, 46, 47, 48, 49, 51, 55, 56, 57,…
class.wr_ <- class.wr %>%
  mutate(region = "wrnf") %>%
  mutate(source = if_else(source=="lc16_evt_200_bin_wrnf_10m", "LANDFIRE EVT", source),
         source = if_else(source=="s2aspen_prob_10m_binOpt_wrnf", "Sentinel-based Map", source),
         source = if_else(source=="usfs_itsp_aspen_ba_gt10_wrnf_10m", "USFS ITSP", source),
         source = if_else(source=="usfs_treemap16_balive_int_bin_wrnf_10m", "USFS TreeMap", source))
patch.wr_ <- patch.wr %>%
  mutate(region = "wrnf") %>%
  mutate(source = if_else(source=="lc16_evt_200_bin_wrnf_10m", "LANDFIRE EVT", source),
         source = if_else(source=="s2aspen_prob_10m_binOpt_wrnf", "Sentinel-based Map", source),
         source = if_else(source=="usfs_itsp_aspen_ba_gt10_wrnf_10m", "USFS ITSP", source),
         source = if_else(source=="usfs_treemap16_balive_int_bin_wrnf_10m", "USFS TreeMap", source),
         area_qt = factor(ntile(area, 4), labels = c("Q1", "Q2", "Q3", "Q4")))
rm(patch.sr,class.sr,patch.wr,class.wr)

Get some summary statistics:

class.sr_ %>%
  group_by(source) %>%
  summarize(
    total_area_km2 = sum(total_area) * 0.01
  ) %>%
  ggplot(aes(x=reorder(source, total_area_km2), y=total_area_km2)) +
  geom_bar(stat="identity", fill="light green") +
  theme_light(12)

Table X: Class-level statistics

df1 <- class.sr_ %>%
  group_by(source) %>%
  summarize(
    total_area_km2 = sum(total_area) * 0.01,
    n_patch_total = sum(n_patch),
    patch_density_mn = mean(patch_den)
  )

df2 <- patch.sr_ %>%
  group_by(source) %>%
  summarize(
    patch_size_mn = mean(area),
    per_ar_r_mn = mean(perimeter_area_ratio)
  )

df <- left_join(df1,df2,by="source")
head(df)

rm(df1,df2)

# Make a tidy table
# Set font name for table
fontname <- "Times New Roman"
# Clean up extra rows and digits
cleaned <- df %>%
 mutate_if(is.double, ~ round(., digits = 2)) %>%
 # Sort from fastest to slowest
 arrange(total_area_km2)
# Fix names and add units
names(cleaned) <- c("Data Source","Total Area (Km2)","Number of Patches","Patch Density",
                    "Average Patch Size","Average Perimeter/Area Ratio")
# Create the flextable
ft <- flextable::flextable(cleaned) %>%
  flextable::font(fontname = fontname, part = "all") %>%
  flextable::autofit() %>% 
  flextable::fit_to_width(7.5)
ft

Data Source

Total Area (Km2)

Number of Patches

Patch Density

Average Patch Size

Average Perimeter/Area Ratio

Sentinel-based Map

9,384.41

1,760,386

35.73

0.53

2,745.57

LANDFIRE EVT

13,441.59

728,370

14.22

1.85

1,060.98

USFS TreeMap

13,931.85

1,268,131

24.82

1.10

1,145.88

USFS ITSP

20,477.69

266,762

4.82

7.68

941.75

print(ft, preview = "docx")

Plot the total area box plot across spatial blocks for the Southern Rockies:


# Combine the White River NF and SRME to plot patch statistics
df <- bind_rows(patch.sr_,patch.wr_)
# Plot facet wrap of patch size
df <- df %>%
  group_by(source,region) %>%
  summarize(
    patch_size = mean(area),
    per_ar_r = mean(perimeter_area_ratio)
  ) 
`summarise()` has grouped output by 'source'. You can override using the `.groups` argument.
glimpse(df)
Rows: 8
Columns: 4
Groups: source [4]
$ source     <chr> "LANDFIRE EVT", "LANDFIRE EVT", "Sentinel-based Map", "Sentinel-based Map", "USFS ITSP", "USFS ITSP", "USFS …
$ region     <chr> "srme", "wrnf", "srme", "wrnf", "srme", "wrnf", "srme", "wrnf"
$ patch_size <dbl> 1.8454344, 3.4151061, 0.5330884, 0.7358793, 7.6763901, 14.8811627, 1.0986130, 2.2167857
$ per_ar_r   <dbl> 1060.9830, 1046.2439, 2745.5691, 2872.1164, 941.7452, 944.5034, 1145.8826, 1125.3520
# Get the class metrics in the same format
df2 <- bind_rows(class.sr_,class.wr_) %>%
  group_by(source,region) %>%
  summarize(
    patch_den = mean(patch_den)
  )
`summarise()` has grouped output by 'source'. You can override using the `.groups` argument.
df_ <- left_join(df,df2,by=c("region","source"))

# Pivot longer

metric_labels <- c(patch_size = "Patch Size (ha)",
                   per_ar_r = "Perimeter-Area Ratio",
                   patch_den = "Patch Density")

df.long <- df_ %>%
  pivot_longer(cols = c(contains("_")),
               names_to = "metric",
               values_to = "value") %>%
  mutate(region = if_else(region=="srme","Southern Rockies","White River NF"),
         source = if_else(source=="Sentinel-based Map","Sentinel-based",source),
         source = factor(source, levels = rev(levels(reorder(source, value)))))

df.long$source <- factor(df.long$source,levels=c('Sentinel-based', 'LANDFIRE EVT', 'USFS TreeMap', 'USFS ITSP'))

cols <- c("#005a32","#a1d99b")

# Grouped bar chart
f7 <- ggplot(data=df.long, aes(x=source, y=value, fill=factor(region))) +
  geom_bar(stat="identity", position=position_dodge()) +
  scale_fill_manual(values=cols) +
  facet_wrap(~metric, scales = "free_y", labeller = labeller(metric = metric_labels)) + # Faceting by summary variable
  labs(x="", y="Value", fill="Region") + # Adding labels
  theme_bw(10) +
  theme(legend.position = "top") +
  theme(axis.text.x = element_text(angle=45, hjust=1)) # Improving readability of x-axis labels
f7


ggsave(f7, file = "../../figures/Figure7_Landscape_Summaries.png", dpi=300)
Saving 6.5 x 3.5 in image

Calculate some grouped statistics (to compare with the class metrics results)


# Also create a grouped summary
(patch.summary <- patch_metrics %>%
  group_by(source,region) %>%
  summarize(area_md = median(area), # convert to hectares
            area_mn = mean(area),
            area_sum = sum(area),
            perim_md = median(perimeter),
            perim_mn = mean(perimeter),
            par_md = median(perimeter_area_ratio),
            par_mn = mean(perimeter_area_ratio),
            si_md = median(shape_index),
            si_mn = mean(shape_index)) %>%
  ungroup())

(patch.long <- patch.summary %>%
  pivot_longer(cols = c(contains("_")),
               names_to = "metric",
               values_to = "value"))

patch.long$source <- factor(patch.long$source,levels=c('Aspen10m', 'Aspen30m', 'LFEVT', 'TreeMap', 'ITSP'))

(ggplot(data = patch.long, aes(x=source, y=value, fill=region)) +
    geom_bar(stat="identity", position="dodge", width=0.7) +
    facet_wrap(~metric, scales="free") + 
    theme_minimal(14) +
    theme(axis.text.x = element_text(angle = 45, hjust = 1),
    text = element_text(family = "Arial")))

(f8s <- ggplot(data = patch.long, aes(x=source, y=value, fill=region)) +
    geom_bar(stat="identity", position="dodge", width=0.7) + 
    theme_minimal(14) +
    theme(axis.text.x = element_text(angle = 45, hjust = 1),
    text = element_text(family = "Arial")))

Grab some summary statistics on the patch dynamics:


a <- patch.long %>% filter(region=="SRME",metric=="area_mn",source=="Aspen10m")
b <- patch.long %>% filter(region=="SRME",metric=="area_mn",source=="LFEVT")

print("Difference in mean patch size for the SRME: ")
((b$value-a$value)/b$value)*100

a <- patch.long %>% filter(region=="WRNF",metric=="area_mn",source=="Aspen10m")
b <- patch.long %>% filter(region=="WRNF",metric=="area_mn",source=="LFEVT")

print("Difference in mean patch size for the WRNF: ")
((b$value-a$value)/b$value)*100

print("~~~~~~~~~~~~~~~~")

print("Difference in mean patch size for the SRME: ")
((b$value-a$value)/b$value)*100

(c <- patch.long %>% filter(metric=="area_md",source=="Aspen10m"))
(d <- patch.long %>% filter(metric=="area_md",source=="LFEVT"))

print("Mean and median patch sizes: ")
paste0("Aspen10m mean: ",a$value)
paste0("LFEVT mean: ",b$value)
paste0("Aspen10m median: ",c$value)
paste0("LFEVT mean: ",d$value)



print("Difference in median area: ")
((d$value-c$value)/d$value)*100

print("Mean Perimeter area ratio: ")
(a <- patch.long %>% filter(metric=="par_mn",source=="Aspen10m"))
(b <- patch.long %>% filter(metric=="par_mn",source=="LFEVT"))

print("Difference in PAR: ")
((b$value-a$value)/b$value)*100

rm(a,b,c,d)

Boxplot as facet wrap for patch metrics:


brewer.pal(n=5,"Set1")

cols <- c("#005a32","#a1d99b")

(df <- patch_metrics %>%
  # mutate(area_ha = area*100) %>%
  select(-c(X,index,shape_index)) %>%
  pivot_longer(contains(c("area","perimeter_area_ratio"))) %>%
  arrange(source, desc(value)) %>%
  mutate(name = as.character(name),
         name = recode(name,
                       "area" = "Patch Size (ha)",
                       "perimeter_area_ratio" = "Perimeter/Area Ratio")))

df$source <- factor(df$source,
                    levels=c('Aspen10m', 'Aspen30m', 'LFEVT', 'TreeMap', 'ITSP'))
 
(f8a <- ggplot(data=df, aes(y = value, x = factor(source), fill = factor(region))) +
  geom_boxplot(outlier.size = 0.2) +
  scale_y_continuous(trans="log10",labels=label_number_si(accuracy = 1)) +
  scale_fill_manual(values=cols) +
  facet_wrap(~name, scales = "free_y", nrow=1) +
  labs(x="\nData Source",y="Value",title="Patch Metrics", fill="Region: ") +
  theme_light(12) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1, size=11),
        axis.text.y = element_text(angle = 0, vjust=0.1, size=11),
        axis.title.y = element_text(size=12, face="italic", vjust=1),
        axis.title.x = element_text(size=12, face="italic", vjust=-1),
        plot.margin = unit(c(0.5,0.5,0.5,0.5),"cm"),
        legend.position="top",
        legend.spacing.x = unit(0.5,"cm"),
        text = element_text(family = "Arial")))

ggsave(f8a, file = "../../figures/Figure8A_patch_metrics.png", dpi = 300) # adjust dpi accordingly

Plot class metrics as facet wrap:


# Calculate area in square meters
srme_area_km2 <- st_area(srme) / 1e6
wrnf_area_km2 <- st_area(wrnf) / 1e6

(df <- class_metrics %>%
  mutate(total_area = total_area*0.01,
         prop_area = if_else(region=="SRME", as.double((total_area/srme_area_km2*100)), 0.0),
         prop_area = if_else(region=="WRNF", as.double((total_area/wrnf_area_km2*100)), prop_area)) %>%
  select(-c(X)) %>%
  pivot_longer(cols = c(contains("_")),
               names_to = "metric",
               values_to = "value") %>%
  mutate(metric = as.character(metric),
         metric = recode(metric,
                         "n_patch" = "Number of Patches",
                         "patch_den" = "Patch Density",
                         "total_area" = "Total area (km2)",
                         "prop_area" = "Proportion of Area")))

df$source <- factor(df$source, levels=c('Aspen10m', 'Aspen30m', 'LFEVT', 'TreeMap', 'ITSP'))
head(df)

(f8b <- ggplot(data=df, aes(y=value, x=factor(source), fill=factor(region))) +
  geom_bar(stat="identity", position="dodge", width=0.7) +
  scale_y_continuous(labels=scales::label_number_si(accuracy = 1)) +
  scale_fill_manual(values=cols) +
  labs(x="", y="Value", title="Landscape Metrics", fill="Region: ") +
  theme(axis.title.x = NULL) +
  facet_wrap(~metric, scales = "free_y") +
  theme_light(12) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1, size=11),
        axis.text.y = element_text(angle = 0, vjust=0.1, size=11),
        axis.title.y = element_text(size=12, face="italic", vjust=0.5),
        axis.title.x = element_text(size=12, face="italic", vjust=-0.5),
        plot.margin = unit(c(0.5,0.5,0.5,0.5),"cm"),
        legend.position = "top",
        legend.spacing.x = unit(0.5,"cm"),
        text = element_text(family = "Arial")))

ggsave(f8b, file = "../../figures/Figure8B_landscape_metrics.png", dpi = 300) # adjust dpi accordingly
(arr <- ggarrange(f8b,f8a,nrow=2,ncol=1,labels=c("A","B"), common.legend = T))
ggsave(arr, file="../../figures/Figure8_Landscape_Patch_Metrics.png", dpi=300)

The 10-m aspen classification identified Xx more patch than reference images:


print("% Difference in number of patches across the Southern Rockies: ")
a <- df %>% filter(region=="SRME",metric=="Number of Patches",source=="Aspen10m")
b <- df %>% filter(region=="SRME",metric=="Number of Patches",source=="LFEVT")
((b$value-a$value)/b$value)*100

print("% Difference in number of patches across the White River NF: ")
a <- df %>% filter(region=="WRNF",metric=="Number of Patches",source=="Aspen10m")
b <- df %>% filter(region=="WRNF",metric=="Number of Patches",source=="LFEVT")
((b$value-a$value)/b$value)*100

print("~~~~~~~~~~~~~")

print("~~~~~~~~~~~~~")

print("% Difference in patch density: ")
a <- df %>% filter(metric=="Patch Density",source=="Aspen10m")
b <- df %>% filter(metric=="Patch Density",source=="LFEVT")
((b$value-a$value)/b$value)*100

print("% Difference in total area:")
a <- df %>% filter(metric=="Total area (km2)",source=="Aspen10m")
aa <- df %>% filter(metric=="Total area (km2)",source=="Aspen30m")
b <- df %>% filter(metric=="Total area (km2)",source=="LFEVT")
((b$value-a$value)/b$value)*100
((b$value-aa$value)/b$value)*100

rm(a,aa,b)

Supplemental

Figure S1: Quaking Aspen Phenology

Load the spatial block grid and phenology time-series summaries by spatial block for the Southern Rockies. Join the phenology summaries to their geometries.

# Load the grids, keep the elevation attribute and TreeMap sum (aspen area)
grid <- st_read('../../data/spatial/mod/boundaries/spatial_block_grid_50km2.gpkg',
                quiet=T) %>%
  select(grid_id,elevation_mn,treemap_sum,treemap_pct)

# Load the phenology by spatial block grid
phenology <- read_csv('../../data/tabular/mod/phenology/viirs_phenology_by_grid_in_aspen.csv',
                      show_col_types = FALSE) %>%
  rename(fid = `system:index`) %>%
  mutate(grid_id = str_sub(fid, -7),
         year = as.integer(str_sub(fid, 1, 4))) %>%
  select(-c(fid,label,count,.geo)) %>%
  # Create DOY versions of the phenology metrics for plotting and statistical analysis
  mutate(
    season_length = Growing_Season_Length_1_median,
    doy_midgreenup = yday(Date_Mid_Greenup_Phase_1_median),
    doy_midsenescence = yday(Date_Mid_Senescence_Phase_1_median),
    doy_green_dec = yday(Onset_Greenness_Decrease_1_median),
    doy_green_inc = yday(Onset_Greenness_Increase_1_median),
    doy_green_max = yday(Onset_Greenness_Maximum_1_median),
    doy_green_min = yday(Onset_Greenness_Minimum_1_median)
  ) %>%
  left_join(grid%>%as_tibble(), by="grid_id") %>%
  select(grid_id,year,elevation_mn,season_length,doy_midgreenup,doy_midsenescence,
         doy_green_dec,doy_green_inc,doy_green_max,doy_green_min)

glimpse(phenology)
Rows: 1,110
Columns: 10
$ grid_id           <chr> "-233+82", "-234+80", "-234+81", "-234+82", "-234+83", "-234+84", "-234+85", "-234+86", "-234+87", "-235+79", "-235+8…
$ year              <int> 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2013, 201…
$ elevation_mn      <dbl> 2116.571, 2115.817, 2178.622, 2414.847, 2247.682, 2034.530, 1711.193, 2381.485, 2113.312, 2315.858, 2724.770, 2936.67…
$ season_length     <dbl> 200, 204, 130, 192, 196, 190, 186, 204, 182, 176, 186, 182, 192, 184, 190, 217, 212, 183, 183, 168, 187, 174, 179, 18…
$ doy_midgreenup    <dbl> 160, 166, 170, 160, 155, 159, 146, 153, 159, 164, 160, 157, 155, 155, 157, 145, 154, 161, 161, 158, 157, 154, 150, 14…
$ doy_midsenescence <dbl> 287, 281, 267, 281, 280, 276, 281, 280, 267, 275, 280, 276, 280, 275, 276, 275, 286, 278, 276, 269, 281, 272, 272, 27…
$ doy_green_dec     <dbl> 254, 241, 263, 248, 248, 247, 241, 247, 237, 247, 248, 246, 249, 245, 244, 241, 250, 250, 247, 237, 244, 237, 245, 24…
$ doy_green_inc     <dbl> 125, 113, 142, 124, 117, 124, 119, 110, 122, 133, 126, 125, 119, 122, 121, 99, 108, 135, 132, 131, 133, 134, 132, 129…
$ doy_green_max     <dbl> 190, 190, 197, 193, 189, 188, 177, 190, 186, 198, 193, 186, 188, 184, 189, 186, 192, 187, 186, 183, 180, 174, 163, 16…
$ doy_green_min     <dbl> 322, 316, 271, 312, 314, 309, 320, 313, 297, 301, 312, 305, 311, 306, 311, 313, 320, 314, 313, 298, 319, 306, 304, 31…

Spatial maps of the key phenology metrics.


# Calculate the median phenology metrics
phenology.md <- phenology %>%
  group_by(grid_id) %>%
  summarise(across(starts_with("doy"), median, na.rm=T))

# Join to the spatial data and pivot longer
grid.l <- grid %>% 
  left_join(phenology.md, by="grid_id") %>%
  pivot_longer(cols = starts_with("doy"), names_to = "metric", values_to = "value") %>%
  filter(treemap_pct > 0.05) # Keep only blocks with some aspen cover

ordered_metrics <- c(
  "doy_green_inc","doy_midgreenup","doy_green_max",
  "doy_green_dec","doy_midsenescence","doy_green_min") 

# Tidy the names for plotting
metric_names <- c(
  doy_green_inc = "Onset Greenness Increase",
  doy_midgreenup = "Mid Greenup Phase",
  doy_green_max = "Onset Greenness Maximum",
  doy_green_dec = "Onset Greenness Decrease",
  doy_midsenescence = "Mid Senescence Phase",
  doy_green_min = "Onset Greenness Minimum"
)

# Fix the factor levels and labels
grid.l$metric <- factor(grid.l$metric, levels = ordered_metrics, labels = metric_names)

head(grid.l)
Simple feature collection with 6 features and 6 fields
Geometry type: POLYGON
Dimension:     XY
Bounding box:  xmin: -798287.5 ymin: 1616723 xmax: -754432.6 ymax: 1670509
Projected CRS: NAD83 / Conus Albers

metrics <- unique(grid.l$metric)

plots <- list()

for (m in metrics){
  # Filter for the metric
  gdf <- grid.l %>% filter(metric == m)
  
  # Map
  p <- ggplot() +
    geom_sf(data=grid, fill=NA) +
    geom_sf(data=gdf, aes(fill=value)) +
    scale_fill_continuous(low = "lightgreen", high = "darkgreen",
                          labels = function(x) format(as.Date(as.numeric(x), origin="1970-01-01"), "%b %d")) +
    geom_sf(data=srme,fill=NA,color="black",size=2.5) +
    labs(fill="", title=m) +
    theme_void(12) +
    theme(legend.position = "right",
          legend.text = element_text(angle = 0, size=9),   # Ensure labels are not rotated
          plot.title = element_text(hjust = 0.5, size=10),
          plot.margin = ggplot2::margin(0.1, 5, 0.1, 5, "mm")
    ) + 
    guides(fill = guide_colourbar(
      barwidth = 0.5, 
      barheight = 6, 
      ticks = FALSE,
      label.position = "right", 
      label.theme = element_text(size = 8))
    )
  # Save the individual map
  ggsave(p, file=paste0("../../figures/FigureS1B_Phenology_by_Elevation",m,".png"), dpi=300)
  print(p)
  plots[[m]] <- p
}
Saving 7 x 7 in image

Create a combined plot for four metrics.


fs1a <- ggarrange(plots[[1]], plots[[3]], plots[[2]], plots[[6]],
                  ncol = 2, nrow = 2, widths=c(1,1,1,1), heights=c(1,1,1,1),
                  align = "hv", labels = c("A","B","C","D"))
fs1a

ggsave(fs1a, file="../../figures/FigureS1A_PhenologyMaps.png", dpi=300)
Saving 6.75 x 6.5 in image

Plot the relationship with elevation across metrics.


metrics <- c("Mid Greenup Phase", "Onset Greenness Decrease", "Mid Senescence Phase", "Onset Greenness Minimum")

df <- grid.l %>% filter(metric %in% metrics)

# Individual plots
for (m in metrics){
  # Filter for the metric
  gdf <- grid.l %>% filter(metric == m)
  
  # Map
  p <- ggplot(data=gdf, aes(x=value,y=elevation_mn)) +
    geom_smooth(method="lm",colour="gray40", fill="gray70", size=0.5) +
    geom_point(size=0.4) +
    theme_bw(7) +
    theme(axis.text = element_blank(),
          axis.title = element_text(size=6),
          axis.ticks = element_blank()) +
    labs(x="Day of Year",y="Elevation")
  ggsave(p, file=paste0("../../figures/FigureS1B_Phenology_by_Elevation_",m,".png"), width=1.5, height=1.5)
  print(p)
}


# Facet plot
(fs1b <- ggplot(data=df, aes(x=value,y=elevation_mn)) +
  geom_smooth(method="lm",colour="gray40", fill="gray70", size=0.8) +
  geom_point(size=0.8) +
  facet_wrap(~metric, scales="free_x", nrow=2) +
  theme_minimal(12) +
  theme(plot.title = element_text(size=10),
        axis.text = element_text(size=8),
        axis.title = element_text(size=10),
        strip.text = element_text(size=8),
        panel.spacing = unit(2.5, "mm")) +
  labs(x="Day of Year",y="Elevation", tag="B"))

# Save out
ggsave(fs1b, file="../../figures/FigureS1B_Phenology_by_Elevation.png", dpi=300)


rm(metrics,df)

Merge the two plots.

(fs1 <- ggarrange(fs1a, fs1b, nrow=2, ncol=1, align="v"))
`geom_smooth()` using formula = 'y ~ x'Warning: Graphs cannot be vertically aligned unless the axis parameter is set. Placing graphs unaligned.

ggsave(fs1, file="../../figures/FigureS1_Phenology.png", dpi=300)
Saving 4.5 x 5 in image
# Reshape from wide to long format
phenology.l <- phenology %>%
  pivot_longer(
    cols = c(doy_midgreenup,doy_midsenescence,doy_green_dec,
             doy_green_inc,doy_green_max,doy_green_min),
    names_to = "metric",
    values_to = "doy"
  )

ordered_metrics <- c(
  "doy_green_inc","doy_midgreenup","doy_green_max",
  "doy_green_dec","doy_midsenescence","doy_green_min") 

metric_names <- c(
  doy_green_inc = "Onset Greenness Increase",
  doy_midgreenup = "Mid Greenup Phase",
  doy_green_max = "Onset Greenness Maximum",
  doy_green_dec = "Onset Greenness Decrease",
  doy_midsenescence = "Mid Senescence Phase",
  doy_green_min = "Onset Greenness Minimum"
)

phenology.l$metric <- factor(
  phenology.l$metric, 
  levels = ordered_metrics, 
  labels = metric_names
)

# Create the plot with facet_wrap
ggplot(phenology.l, aes(x=factor(year), y=doy)) +
  geom_boxplot() +
  facet_wrap(~ metric, scales = "fixed") +
  theme_minimal() +
  labs(title = "DOY Phenology Metrics (2013-2022)",
       x = "Year",
       y = "Day of Year",
       fill = "Year") +
  theme_light() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))


# Stacked box plots
ggplot(phenology.l, aes(x=factor(year), y=doy, fill=metric)) +
  geom_boxplot(width=1) +
  theme_minimal() +
  labs(x = "Year",
       y = "Day of Year",
       fill = "Metric") +
  theme_light() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1),
        legend.position = "top")

ROC, F1, MCC:


accmeas <- read_csv('../../data/tabular/mod/results/accmeas_prop.csv')

# Calculate the averages for each cutoff value across model runs

accmeas.mn <- accmeas %>%
  mutate(cutoff = as.character(cutoff)) %>%
  group_by(cutoff) %>%
  summarise(
    prMn = mean(prSize,na.rm=T), # size of presence data (mean)
    prSd = sd(prSize,na.rm=T), # size of presence data (sd)
    bgMn = mean(bgSize,na.rm=T), # size of background data (mean)
    bgSd = sd(bgSize,na.rm=T), # size of background data (sd)
    fprMn = mean(fpr,na.rm=T), # False Positive Rate (mean)
    fprSd = sd(fpr,na.rm=T),
    tprMn = mean(tpr,na.rm=T), # True Positive Rate (mean)
    tprSd = sd(tpr,na.rm=T),
    precision = mean(precision,na.rm=T), # precision (mean)
    precisionSd = sd(precision,na.rm=T), 
    recall = mean(recall,na.rm=T), # recall (mean)
    recallSd = sd(recall,na.rm=T),
    f1 = mean(f1,na.rm=T), # F1 score (mean)
    f1Sd = sd(f1,na.rm=T),
    gmean = mean(gmean,na.rm=T), # Geometric Mean (mean)
    gmeanSd = sd(gmean,na.rm=T),
    mcc = mean(mcc,na.rm=T), # Matthew's Correlation Coefficient (mean)
    mccSd = sd(mcc,na.rm=T),
    accuracy = mean(accuracy,na.rm=T), # Overall accuracy (mean)
    accuracySd = sd(accuracy,na.rm=T)
  ) %>%
  ungroup() %>%
  mutate(cutoff = round(as.double(cutoff),3))

# Calculate optimum threshold based on F1 statistic

cutoffOptMn = accmeas.mn[which.max(accmeas.mn$f1),]$cutoff
precisionOptF1Mn = accmeas.mn[which.max(accmeas.mn$f1),]$precision
recallOptF1Mn = accmeas.mn[which.max(accmeas.mn$f1),]$recall
fprOptMn = accmeas.mn[which.max(accmeas.mn$f1),]$fprMn
tprOptMn = accmeas.mn[which.max(accmeas.mn$f1),]$tprMn
max_f1 <- max(accmeas.mn$f1, na.rm = TRUE)

# ROC Curve and label the AUC value (approximation)

# Get the AUC approx

# Ensure data is sorted by FPR
accmeas_mn_sorted <- accmeas.mn[order(accmeas.mn$fprMn), ]
# Compute AUC using trapz function in 'pracma'
auc_avg_approx <- trapz(accmeas_mn_sorted$fprMn, accmeas_mn_sorted$tprMn)

fs2a <- ggplot(data=accmeas) +
  geom_line(aes(x=fpr,y=tpr,color=factor(model)),linewidth=0.4) +
  scale_color_viridis_d(option="turbo") +
  geom_line(data=accmeas.mn, aes(x = fprMn, y = tprMn), color = "black", size = 0.8) +  # ROC curve average
  scale_x_continuous(limits=c(0,1)) +  # Set x axis limits from 0 to 1
  scale_y_continuous(limits=c(0,1)) +
  labs(x='False Positive Rate', y='True Positive Rate', tag="A") +
  geom_point(aes(x = fprOptMn, y = tprOptMn),
             color = "red", size = 3, shape = 19) +  # optimal threshold point
  geom_text(aes(x=fprOptMn, y=tprOptMn),
            label = paste0('Optimal threshold: ', round(cutoffOptMn,3),'\nApprox. AUC: ', round(auc_avg_approx,3)),
            nudge_x = 0.38, nudge_y = -0.08, size = 3.5) +
  coord_fixed(ratio = 1) +  # to keep the x and y axes scales same
  geom_abline(intercept = 0, slope = 1, linetype = "dashed", color = "black") +  # diagonal
  theme_light() +
  theme(legend.position = "none",
        axis.title.y = element_text(size=11,face="italic",
                                    margin = unit(c(0,0.4,0,0), "cm")),
        axis.title.x = element_text(size=11,face="italic",
                                    margin = unit(c(0.4,0,0,0), "cm")),
        axis.text = element_text(size=10),
        strip.text.x = element_text(size = 11))

fs2b <- ggplot(data=accmeas) +
  geom_line(aes(x=cutoff,y=f1,color=factor(model)),linewidth=0.4) +
  scale_color_viridis_d(option="turbo") +
  geom_line(data=accmeas.mn, aes(x=cutoff, y = f1), color = "black", size = 0.8) +  # ROC curve average
  # geom_vline(xintercept=0.424, linetype="dashed", color="black") +
  geom_point(aes(x=cutoffOptMn, y=max_f1),
             color = "red", size = 3, shape = 19) +
  geom_text(aes(x=cutoffOptMn, y=max_f1),
            label = paste0('Optimal threshold: ', round(cutoffOptMn,3)),
            nudge_x = 0.0, nudge_y = 0.08, size = 3.5) +
  scale_x_continuous(limits=c(0,1)) +  # Set x axis limits from 0 to 1
  scale_y_continuous(limits=c(0,1)) +
  labs(x='Threshold', y='F1 Score', tag="B") +
  coord_fixed(ratio = 1) +  # to keep the x and y axes scales same
  theme_light() +
  theme(legend.position = "none",
        axis.title.y = element_text(size=11,face="italic",
                                    margin = unit(c(0,0.4,0,0), "cm")),
        axis.title.x = element_text(size=11,face="italic",
                                    margin = unit(c(0.4,0,0,0), "cm")),
        axis.text = element_text(size=10),
        strip.text.x = element_text(size = 11))

(fs2 <- ggarrange(fs2a,fs2b))

ggsave(fs2, file="../../figures/FigureS2_AUC_F1Max.png", dpi=300)

rm(fs2,fs2a,fs2b,accmeas_mn_sorted,auc_avg_approx)

Table S1: Performance metrics for the optimum cutoff value

Create a pretty table.


library(flextable, quietly = T)

# Get the best F1 score for each model run
accmeas.best <- accmeas %>% 
  mutate(model = as.factor(model)) %>%
  group_by(model) %>% 
  top_n(1,f1) %>%
  ungroup() %>%
  mutate(model_iter = row_number()) %>%
  select(model,prSize,bgSize,tpr,precision,recall,f1,cutoff)

head(accmeas.best,10)

# Fix names and add units
names(accmeas.best) <- c("Model Seed",
                         "# of Presence",
                         "# of Background",
                         "True Positive Rate",
                         "Precision",
                         "Recall",
                         "F1-Score",
                         "Threshold")

# Set font name for table
fontname <- "Times New Roman"

# Create the flextable
(ft1 <- flextable(accmeas.best) %>%
 font(fontname = fontname, part = "all") %>%
 autofit() %>% fit_to_width(6.5))
print(ft1, preview = "docx")

# Write to a CSV
write_csv(accmeas.best, "../../figures/TableS1_Accuracy_MaxF1.csv")

Clean up!

LS0tCnRpdGxlOiAiRmlndXJlcyIKb3V0cHV0OiBodG1sX25vdGVib29rCnJvb3QuZGlyOiAnfi9MaWJyYXJ5L0Nsb3VkU3RvcmFnZS9PbmVEcml2ZS1QZXJzb25hbC9tY29vay9hc3Blbi1maXJlJwotLS0KCmBgYHtyIGluY2x1ZGU9Rn0Kc291cmNlKCdzZXR1cC5SJykKYGBgCgojIFN0dWR5IFJlZ2lvbgoKYGBge3J9CmdncGxvdCgpICsKICBnZW9tX3NmKGRhdGE9c3JtZSkgKwogIGdlb21fc2YoZGF0YT13cm5mLCBmaWxsPSJkYXJrZ3JleSIpICsKICBnZW9tX3NmKGRhdGE9YmxvY2tzLCBmaWxsPU5BLCBsd2Q9MC40KSArCiAgY29vcmRfc2YoY3JzPSJFUFNHOjMyNjEzIikgKwogIHRoZW1lX2xpZ2h0KDExKQpgYGAKCiMgU3BlY3RyYWwgUmVzcG9uc2UgCgpMb2FkIHRoZSBTZW50aW5lbC0yIGFubnVhbCB0aW1lLXNlcmllcyAoMjAxOSkuIAoKYGBge3J9CgojIFNwZWN0cmFsIFJlc3BvbnNlCnRzIDwtIHJlYWRfY3N2KCcuLi8uLi9kYXRhL3RhYnVsYXIvbW9kL3Jlc3VsdHMvczJtc2lfbDJhX2FzcGVuX1RTeTIwMTkuY3N2JywgCiAgICAgICAgICAgICAgIHNob3dfY29sX3R5cGVzID0gRkFMU0UpICU+JSAKICBzZWxlY3QoLWMoaWQsLmdlbyxUQ0IsVENHLFRDVykpICU+JQogIHJlbmFtZShORFZJNzA1ID0gTkRSRSkKCmhlYWQodHMpCgpgYGAKClRpZHkgdGhlIHRpbWUtc2VyaWVzIGRhdGEuCgoxLiBSZW1vdmUgY2xvdWQtY29udGFtaW5hdGVkIHBpeGVscywKMi4gUGl2b3QgdGhlIHRhYmxlIHRvIGdhdGhlciBiYW5kcywKMy4gR2VuZXJhdGUgbWVhbi9tZWRpYW4vc3RkZXYgb2YgcmVmbGVjdGFuY2UgYnkgaW1hZ2UgZGF0ZSwKNC4gQ2FsY3VsYXRlIHdlZWtseSBzdW1tYXJpZXMKCmBgYHtyfQoKIyBGaWx0ZXIgb3V0IGNsb3VkLWNvbnRhbWluYXRlZCBwaXhlbHMgYmFzZWQgb24gdGhlIENsb3VkIFNjb3JlICsgdmFsdWVzCiMgUmVmZXJlbmNlOiAgaHR0cHM6Ly9tZWRpdW0uY29tL2dvb2dsZS1lYXJ0aC9hbGwtY2xlYXItd2l0aC1jbG91ZC1zY29yZS1iZDZlZTJlMjIzNWUKCnRzcCA8LSB0cyAlPiUKICBmaWx0ZXIoCiAgICBjcyA+PSAwLjgsICMgPj0gQ2xvdWQgU2NvcmUgKyBjb250YW1pbmF0aW9uCiAgKSAlPiUKICAjIGFkZCB0aGUgbW9udGggYXMgYSBjb2x1bW4KICBtdXRhdGUobW9udGggPSBtb250aChpbWFnZV9kYXRlLCBsYWJlbD1UUlVFLCBhYmJyPVRSVUUpKSAlPiUKICAjIEZpbHRlciBudWxsIHZhbHVlcwogIGZpbHRlcihjb21wbGV0ZS5jYXNlcyguKSkKCiMgVGVzdCB1c2luZyBzb21lIHZpc3VhbGl6YXRpb25zCgojIE5EVkk3MDUgc2NhdHRlciBwbG90IHdpdGggdHJlbmQgbGluZQp0c3AgJT4lCiAgZ3JvdXBfYnkoaW1hZ2VfZGF0ZSkgJT4lCiAgc3VtbWFyaXplKGJhbmQgPSBtZWRpYW4oTkRWSTcwNSkpICU+JQogIGdncGxvdChhZXMoeD1pbWFnZV9kYXRlLHk9YmFuZCkpICsKICBnZW9tX3BvaW50KHNpemU9MC44KSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kPSJsb2VzcyIpICsKICBsYWJzKHg9IkltYWdlIERhdGUiLCB5PSJORFJFIikgKwogIHRoZW1lX2J3KDExKQoKIyBORFZJNzA1IGJveCBwbG90CmdncGxvdChkYXRhPXRzcCwgYWVzKHg9TkRWSTcwNSwgeT1tb250aCkpICsKICBnZW9tX2JveHBsb3QoKSArCiAgY29vcmRfZmxpcCgpICsKICBsYWJzKHk9IkltYWdlIERhdGUiLCB4PSJORFJFIikgKwogIHRoZW1lX2J3KDExKQoKcm0odHMpCgpgYGAKCk5vdyBwaXZvdCB0aGUgdGFibGUgbG9uZ2VyOgoKYGBge3J9CgojIENyZWF0ZSBhIGxvbmdlciBkYXRhIGZyYW1lCnRzcF8gPC0gdHNwICU+JQogIHNlbGVjdChjKHN0YXJ0c193aXRoKCJCIiksaW1hZ2VfZGF0ZSwKICAgICAgICAgICBTTEFWSSxORFZJNzA1LE5ETUksQ2hsUkUsSVJFQ0ksTUNBUkkpKSAlPiUKICBncm91cF9ieShpbWFnZV9kYXRlKSAlPiUKICBzdW1tYXJpemVfYWxsKGxpc3QobWVkaWFuKSkgJT4lCiAgcGl2b3RfbG9uZ2VyKAogICAgY29scyA9IC1jKGltYWdlX2RhdGUpLAogICAgbmFtZXNfdG89ImJhbmQiLAogICAgdmFsdWVzX3RvPSJyZWZsZWN0YW5jZSIKICApICU+JQogICMgYWRkIGEgbW9udGggYW5kIHdlZWsgbGFiZWwKICBtdXRhdGUoCiAgICBtb250aCA9IG1vbnRoKGltYWdlX2RhdGUsIGxhYmVsPVRSVUUsIGFiYnI9VFJVRSksCiAgICB3ZWVrID0gaXNvd2VlayhpbWFnZV9kYXRlKSwKICAgIGJpd2VlayA9IGN1dC5EYXRlKGltYWdlX2RhdGUsIGJyZWFrcz0iMiB3ZWVrIiwgbGFiZWxzPUZBTFNFKQogICkKCmhlYWQodHNwXywgbj0xOSkKCnJtKHRzcCkKZ2MoKQoKYGBgCgpCcmVha3BvaW50IGFuYWx5c2lzIHRvIGlkZW50aWZ5IHNpZ25pZmljYW50IHNoaWZ0IGluIHNwZWN0cmFsIHNpZ25hdHVyZSB0aHJvdWdob3V0IHRoZSB5ZWFyIGJhc2VkIG9uIE5EUkU6CgpgYGB7cn0KCiMgSXNvbGF0ZSBvbmUgb2YgdGhlIHZlZ2V0YXRpb24gaW5kaWNlcyAoTm9ybWFsaXplZCBEaWZmZXJlbmNlIFJlZC1lZGdlIEluZGV4KQpkZiA8LSB0c3BfICU+JSAKICBmaWx0ZXIoYmFuZCA9PSAiTkRWSTcwNSIpICU+JSAKICBzZWxlY3QoaW1hZ2VfZGF0ZSx3ZWVrLHJlZmxlY3RhbmNlKSAlPiUKICBncm91cF9ieShpbWFnZV9kYXRlLHdlZWspICU+JQogIHN1bW1hcml6ZShyZWZsZWN0YW5jZSA9IG1lZGlhbihyZWZsZWN0YW5jZSkpICU+JQogIHVuZ3JvdXAoKQoKIyBDcmVhdGUgYSB3ZWVrbHkgdGltZS1zZXJpZXMgb2JqZWN0CmRmLnRzIDwtIHRzKGRmJHJlZmxlY3RhbmNlLCBmcmVxdWVuY3kgPSA1MikKCiMgSW1wbGVtZW50IHRoZSBjaGFuZ2Vwb2ludCBhbmFseXNpcyBiYXNlZCBvbiBORFJFCnJlc3VsdCA8LSBjaGFuZ2Vwb2ludDo6Y3B0Lm1lYW52YXIoZGYudHMsIG1ldGhvZD0iUEVMVCIpCmJwLmluZCA8LSBjaGFuZ2Vwb2ludDo6Y3B0cyhyZXN1bHQpCgojIE1hcCBicmVha3BvaW50cyB0byBkYXRlcwpwcmludCgiQnJlYWtwb2ludCB3ZWVrczoiKQpicC5kYXRlcyA8LSBkZiR3ZWVrW2JwLmluZF0KYnAuZGF0ZXMKCiMgUGxvdCB0aGUgcmVzdWx0CnBsb3QocmVzdWx0KQoKcm0oZGYsIGRmLnRzLCByZXN1bHQsIGJwLmluZCwgYnAuZGF0ZXMpCgpgYGAKClBlcmZvcm0gdGhlIGFuYWx5c2lzIGJ5IHNwYXRpYWwgYmxvY2sgZ3JpZCB0byBnZXQgYW4gaWRlYSBvZiB0aGUgdmFyaWF0aW9uIGluIHRpbWUtc2VyaWVzIGJyZWFrcG9pbnRzLgoKUGxvdCBvZiBtZWRpYW4gZGFpbHkgb2JzZXJ2YXRpb25zOgoKYGBge3IgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9OX0KCiMgUmVvcmRlciBiYW5kIGZhY3RvcnMKdHNwXyRiYW5kIDwtIGZhY3Rvcih0c3BfJGJhbmQsIAogICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gYygiQjIiLCJCMyIsIkI0IiwiQjUiLCJCNiIsIkI3IiwiQjgiLCJCOEEiLCJCMTEiLCJCMTIiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQ2hsUkUiLCJJUkVDSSIsIk5ETUkiLCJORFZJNzA1IiwiU0xBVkkiLCJNQ0FSSSIpKQoKdHNwXyAlPiUKICBmaWx0ZXIoc3RyX2RldGVjdChiYW5kLCJCIikpICU+JQogIGdncGxvdChhZXMoeD1pbWFnZV9kYXRlLHk9cmVmbGVjdGFuY2UgLGNvbG9yPWZhY3RvcihiYW5kKSkpICsKICBnZW9tX2xpbmUobGluZXdpZHRoPTAuNCkgKwogIHNjYWxlX2NvbG9yX3ZpcmlkaXNfZChvcHRpb249InR1cmJvIikgKwogIGxhYnMoeD0iQWNxdWlzaXRpb24gRGF0ZSIseT0iUmVmbGVjdGFuY2UiLGNvbG9yPSJTMiBNU0kgQmFuZCIpICsKICB0aGVtZV9idygxNCkgKwogIHRoZW1lKGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChzaXplPTExLGZhY2U9Iml0YWxpYyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1hcmdpbiA9IHVuaXQoYygwLDAuNCwwLDApLCAiY20iKSksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF90ZXh0KHNpemU9MTEsZmFjZT0iaXRhbGljIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWFyZ2luID0gdW5pdChjKDAuNCwwLDAsMCksICJjbSIpKSwKICAgICAgICBheGlzLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT0xMCksCiAgICAgICAgbGVnZW5kLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT0xMCksCiAgICAgICAgbGVnZW5kLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9MTEpKQpgYGAKCiMjIEZpZ3VyZSAzOiBTcGVjdHJhbCBSZXNwb25zZSBvZiBBc3BlbiBGb3Jlc3QgUHJlc2VuY2UgRGF0YQoKR2F0aGVyIHRoZSBzZWFzb25hbCBjb21wb3NpdGUgc3RhcnQgYW5kIGVuZCBkYXRlczoKCmBgYHtyfQojIFN1bW1lcgooc3RhcnRfc3VtbWVyIDwtIHdlZWsoYXMuRGF0ZSgiMjAxOS0wNS0yNSIpKSkKKGVuZF9zdW1tZXIgPC0gd2Vlayhhcy5EYXRlKCIyMDE5LTA4LTEzIikpKQojIEF1dHVtbgooc3RhcnRfYXV0dW1uIDwtIHdlZWsoYXMuRGF0ZSgiMjAxOS0wOS0wMiIpKSkKKGVuZF9hdXR1bW4gPC0gd2Vlayhhcy5EYXRlKCIyMDE5LTExLTE0IikpKQpgYGAKCkdlbmVyYXRlIHRoZSB3ZWVrbHkgbWVkaWFuIHJlZmxlY3RhbmNlLgoKYGBge3Igd2FybmluZz1GLCBtZXNzYWdlPUYsIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTl9CgojIExpc3QgZm9yIGJhbmQgY29sb3JzCgpiYW5kX2NvbG9ycyA8LSBjKAogICJCMiIgID0gIiMxRTkwRkYiLCAgIyBCbHVlOiBEb2RnZXIgQmx1ZQogICJCMyIgID0gIiMwMEZGN0YiLCAgIyBHcmVlbjogU3ByaW5nIEdyZWVuCiAgIkI0IiAgPSAiI0ZGMDAwMCIsICAjIFJlZDogUHVyZSBSZWQKICAiQjUiICA9ICIjRkZBMDdBIiwgICMgUmVkIEVkZ2U6IExpZ2h0IFNhbG1vbgogICJCNiIgID0gIiNGRjhDMDAiLCAgIyBOSVI6IERhcmsgT3JhbmdlCiAgIkI3IiAgPSAiI0QyNjkxRSIsICAjIE5JUjogQ2hvY29sYXRlCiAgIkI4IiAgPSAiI0ZGRDcwMCIsICAjIE5JUjogR29sZAogICJCOEEiID0gIiNCODg2MEIiLCAgIyBOSVI6IERhcmsgR29sZGVucm9kCiAgIkIxMSIgPSAiIzAwOEI4QiIsICAjIFNXSVI6IERhcmsgQ3lhbiAoZGFyayBzaGFkZSBvZiB0ZWFsKQogICJCMTIiID0gIiM4MDAwODAiICAgIyBTV0lSOiBQdXJwbGUKKQoKCiMgR3JvdXAgYnkgd2VlaywgY2FsY3VsYXRlIHRoZSBtZWFuLCBwbG90CgpmM2EgPC0gdHNwXyAlPiUKICBmaWx0ZXIoc3RyX2RldGVjdChiYW5kLCJCIikpICU+JQogIGdyb3VwX2J5KGJhbmQsd2VlaykgJT4lCiAgc3VtbWFyaXplKHJlZmwgPSBtZWFuKHJlZmxlY3RhbmNlKSkgJT4lCiAgZ2dwbG90KGFlcyh4PXdlZWsseT1yZWZsLGNvbG9yPWZhY3RvcihiYW5kKSkpICsKICBnZW9tX2xpbmUobGluZXdpZHRoPTAuNikgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBiYW5kX2NvbG9ycykgKwogIGxhYnMoeD0iQWNxdWlzaXRpb24gV2VlayIseT0iUmVmbGVjdGFuY2UiLGNvbG9yPU5VTEwpICsKICB0aGVtZV9idygxMSkgKwogIHRoZW1lKGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChzaXplPTExLGZhY2U9Iml0YWxpYyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1hcmdpbiA9IHVuaXQoYygwLDAuNCwwLDApLCAiY20iKSksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF90ZXh0KHNpemU9MTEsZmFjZT0iaXRhbGljIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWFyZ2luID0gdW5pdChjKDAuNCwwLDAsMCksICJjbSIpKSwKICAgICAgICBheGlzLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT0xMCksCiAgICAgICAgbGVnZW5kLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT04KSwKICAgICAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT04KSwKICAgICAgICBsZWdlbmQucG9zaXRpb24gPSBjKDAuMiwwLjk1KSwKICAgICAgICBsZWdlbmQuZGlyZWN0aW9uID0gImhvcml6b250YWwiLAogICAgICAgIGxlZ2VuZC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGw9IndoaXRlIiwgY29sb3I9ImJsYWNrIiksCiAgICAgICAgcGxvdC5tYXJnaW4gPSB1bml0KGMoMS4yLDAuNSwxLjIsMC41KSwgJ2xpbmVzJyksCiAgICAgICAgdGV4dCA9IGVsZW1lbnRfdGV4dChmYW1pbHkgPSAiQXJpYWwiKSkgKwogIGFubm90YXRlKCdyZWN0JywgeG1pbj1zdGFydF9zdW1tZXIsIHhtYXg9ZW5kX3N1bW1lciwgeW1pbj0wLCB5bWF4PTYwMDAsIGFscGhhPS4zLCBmaWxsPSdncmF5JykgKwogIGFubm90YXRlKCdyZWN0JywgeG1pbj1zdGFydF9hdXR1bW4sIHhtYXg9ZW5kX2F1dHVtbiwgeW1pbj0wLCB5bWF4PTYwMDAsIGFscGhhPS4zLCBmaWxsPSdncmF5JykgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdD1zdGFydF9zdW1tZXIsIGxpbmV0eXBlPSJkb3R0ZWQiLCBsd2Q9MC44KSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PWVuZF9zdW1tZXIsIGxpbmV0eXBlPSJkb3R0ZWQiLCBsd2Q9MC44KSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PXN0YXJ0X2F1dHVtbiwgbGluZXR5cGU9ImRvdHRlZCIsIGx3ZD0wLjgpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9ZW5kX2F1dHVtbiwgbGluZXR5cGU9ImRvdHRlZCIsIGx3ZD0wLjgpCgpmM2EKYGBgCgojIyBGaWd1cmUgMUI6IFRpbWUtc2VyaWVzIGNoYXJ0cyBvZiB0aGUgc3BlY3RyYWwgaW5kaWNlczoKCmBgYHtyIG1lc3NhZ2U9Riwgd2FybmluZz1GfQoKIyBBbGwgYmFuZHMKCiMgQ2FsY3VsYXRlIHRoZSBtYXhpbXVtIHJlZmxlY3RhbmNlIGZvciBlYWNoIGJhbmQKbWF4X3JlZmxfYmFuZCA8LSB0c3BfICU+JQogIGdyb3VwX2J5KGJhbmQpICU+JQogIHN1bW1hcml6ZShtYXhfcmVmbCA9IG1heChyZWZsZWN0YW5jZSkpCgojIE5vdyBjcmVhdGUgeW91ciBwbG90CnRzcF8gJT4lCiAgZ3JvdXBfYnkoYmFuZCwgd2VlaykgJT4lCiAgc3VtbWFyaXplKHJlZmwgPSBtZWFuKHJlZmxlY3RhbmNlKSwgLmdyb3VwcyA9ICdkcm9wJykgJT4lCiAgZ2dwbG90KGFlcyh4ID0gd2VlaywgeSA9IHJlZmwsIGdyb3VwID0gMSkpICsKICBnZW9tX2xpbmUoKSArCiAgZ2VvbV9yZWN0KGRhdGEgPSBtYXhfcmVmbF9iYW5kLCBhZXMoeG1pbj1zdGFydF9zdW1tZXIsIHhtYXg9ZW5kX3N1bW1lciwgeW1pbj0wLCB5bWF4PW1heF9yZWZsKSwgYWxwaGE9LjMsIGZpbGw9J2dyYXknLAogICAgICAgICAgICBpbmhlcml0LmFlcyA9IEZBTFNFKSArCiAgZ2VvbV9yZWN0KGRhdGEgPSBtYXhfcmVmbF9iYW5kLCBhZXMoeG1pbj1zdGFydF9hdXR1bW4sIHhtYXg9ZW5kX2F1dHVtbiwgeW1pbj0wLCB5bWF4PW1heF9yZWZsKSwgYWxwaGE9LjMsIGZpbGw9J2dyYXknLAogICAgICAgICAgICBpbmhlcml0LmFlcyA9IEZBTFNFKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gc3RhcnRfc3VtbWVyLCBsaW5ldHlwZSA9ICJkb3R0ZWQiLCBsd2QgPSAwLjgpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSBlbmRfc3VtbWVyLCBsaW5ldHlwZSA9ICJkb3R0ZWQiLCBsd2QgPSAwLjgpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSBzdGFydF9hdXR1bW4sIGxpbmV0eXBlID0gImRvdHRlZCIsIGx3ZCA9IDAuOCkgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IGVuZF9hdXR1bW4sIGxpbmV0eXBlID0gImRvdHRlZCIsIGx3ZCA9IDAuOCkgKwogIGZhY2V0X3dyYXAoLiB+IGJhbmQsIHNjYWxlcyA9ICJmcmVlX3kiKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZShheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDgsIGZhY2UgPSAiaXRhbGljIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWFyZ2luID0gdW5pdChjKDAsIDAuNCwgMCwgMCksICJjbSIpKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDgsIGZhY2UgPSAiaXRhbGljIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWFyZ2luID0gdW5pdChjKDAuNCwgMCwgMCwgMCksICJjbSIpKSwKICAgICAgICBheGlzLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDcpLAogICAgICAgIHN0cmlwLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gOCkpCgojIFNwZWN0cmFsIGluZGljZXMKCm1heF9yZWZsX2JhbmRfdmkgPC0gbWF4X3JlZmxfYmFuZCAlPiUKICBmaWx0ZXIoIXN0cl9kZXRlY3QoYmFuZCwiQiIpKQoKZjNiIDwtIHRzcF8gJT4lCiAgZmlsdGVyKCFzdHJfZGV0ZWN0KGJhbmQsIkIiKSkgJT4lCiAgZ3JvdXBfYnkoYmFuZCx3ZWVrKSAlPiUKICBzdW1tYXJpemUocmVmbCA9IG1lYW4ocmVmbGVjdGFuY2UpKSAlPiUKICBnZ3Bsb3QoYWVzKHg9d2VlaywgeT1yZWZsLCBncm91cD0xKSkgKwogIGdlb21fbGluZShsaW5ld2lkdGg9MC42KSArCiAgbGFicyh4PSJBY3F1aXNpdGlvbiBXZWVrIix5PSIiKSArCiAgZ2VvbV9yZWN0KGRhdGEgPSBtYXhfcmVmbF9iYW5kX3ZpLCBhZXMoeG1pbiA9IHN0YXJ0X3N1bW1lciwgeG1heCA9IGVuZF9zdW1tZXIsIHltaW4gPSAwLCB5bWF4ID0gbWF4X3JlZmwpLCBhbHBoYSA9IC4zLCBmaWxsID0gJ2dyYXknLAogICAgICAgICAgICBpbmhlcml0LmFlcyA9IEZBTFNFKSArCiAgZ2VvbV9yZWN0KGRhdGEgPSBtYXhfcmVmbF9iYW5kX3ZpLCBhZXMoeG1pbiA9IHN0YXJ0X2F1dHVtbiwgeG1heCA9IGVuZF9hdXR1bW4sIHltaW4gPSAwLCB5bWF4ID0gbWF4X3JlZmwpLCBhbHBoYSA9IC4zLCBmaWxsID0gJ2dyYXknLAogICAgICAgICAgICBpbmhlcml0LmFlcyA9IEZBTFNFKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PXN0YXJ0X3N1bW1lciwgbGluZXR5cGU9ImRvdHRlZCIsIGx3ZD0wLjgpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9ZW5kX3N1bW1lciwgbGluZXR5cGU9ImRvdHRlZCIsIGx3ZD0wLjgpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9c3RhcnRfYXV0dW1uLCBsaW5ldHlwZT0iZG90dGVkIiwgbHdkPTAuOCkgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdD1lbmRfYXV0dW1uLCBsaW5ldHlwZT0iZG90dGVkIiwgbHdkPTAuOCkgKwogIGZhY2V0X3dyYXAoLiB+IGJhbmQsIHNjYWxlcyA9ICJmcmVlIikgKwogIHRoZW1lX2xpZ2h0KDExKSArCiAgdGhlbWUoYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemU9MTEsZmFjZT0iaXRhbGljIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWFyZ2luID0gdW5pdChjKDAsMC40LDAsMCksICJjbSIpKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoc2l6ZT0xMSxmYWNlPSJpdGFsaWMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXJnaW4gPSB1bml0KGMoMC40LDAsMCwwKSwgImNtIikpLAogICAgICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplPTEwKSwKICAgICAgICBzdHJpcC50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDExKSwKICAgICAgICBwbG90Lm1hcmdpbiA9IHVuaXQoYygxLjIsMC41LDEuMiwwLjUpLCAnbGluZXMnKSwKICAgICAgICB0ZXh0ID0gZWxlbWVudF90ZXh0KGZhbWlseSA9ICJBcmlhbCIpKQpmM2IKCnJtKG1heF9yZWZsX2JhbmQsbWF4X3JlZmxfYmFuZF92aSkKCmBgYAoKIyMgRmlndXJlIDNDOiBCb3hwbG90IG9mIHNlYXNvbmFsIGNvbXBvc2l0ZXM6CgpgYGB7cn0KCmNvbHMgPC0gYygKICAiU3VtbWVyIiA9ICIjODdDRUVCIiwgCiAgIkF1dHVtbiIgPSAiI0RBQTUyMCIKKQoKc2Vhc29uYWwgPC0gdHNwXyAlPiUKICBtdXRhdGUoc2Vhc29uID0gaWZfZWxzZSh3ZWVrPj0yMiZ3ZWVrPD0zNywiU3VtbWVyIiwibmEiKSwKICAgICAgICAgc2Vhc29uID0gaWZfZWxzZSh3ZWVrPj0zNyZ3ZWVrPD00OCwiQXV0dW1uIixzZWFzb24pKSAlPiUKICBmaWx0ZXIoc2Vhc29uIT0ibmEiKQoKc2Vhc29uYWwkc2Vhc29uIDwtIGZhY3RvcihzZWFzb25hbCRzZWFzb24sIGxldmVscyA9IGMoIlN1bW1lciIsIkF1dHVtbiIpKQogIApmM2MgPC0gc2Vhc29uYWwgJT4lCiAgZmlsdGVyKHN0cl9kZXRlY3QoYmFuZCwiQiIpKSAlPiUKICBnZ3Bsb3QoYWVzKHg9YmFuZCx5PXJlZmxlY3RhbmNlLGZpbGw9c2Vhc29uKSkgKwogIGdlb21fYm94cGxvdChvdXRsaWVyLnNpemUgPSAwLjUsIG91dGxpZXIuY29sb3IgPSAiZ3JleTMwIikgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbHMpICsKICB0aGVtZV9idygxMikgKwogIGxhYnMoeD0iU2VudGluZWwtMiBNU0kgQmFuZCIseT0iUmVmbGVjdGFuY2UiLGZpbGw9IlNlYXNvbjogIikgKwogIHRoZW1lKGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChzaXplPTExLGZhY2U9Iml0YWxpYyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1hcmdpbiA9IHVuaXQoYygwLDAuNCwwLDApLCAiY20iKSksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF90ZXh0KHNpemU9MTEsZmFjZT0iaXRhbGljIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWFyZ2luID0gdW5pdChjKDAuNCwwLDAsMCksICJjbSIpKSwKICAgICAgICBheGlzLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT0xMCksCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLAogICAgICAgIHBsb3QubWFyZ2luID0gdW5pdChjKDEuMiwwLjUsMS4yLDAuNSksICdsaW5lcycpLAogICAgICAgIHRleHQgPSBlbGVtZW50X3RleHQoZmFtaWx5ID0gIkFyaWFsIikpCmYzYwoKYGBgCgojIyBGaWd1cmUgMUQ6IFNlYXNvbmFsIGRpZmZlcmVuY2VzIGluIHRoZSBzcGVjdHJhbCBpbmRpY2VzCgpgYGB7cn0KCmYzZCA8LSBzZWFzb25hbCAlPiUKICBmaWx0ZXIoIXN0cl9kZXRlY3QoYmFuZCwiQiIpKSAlPiUKICBnZ3Bsb3QoYWVzKHk9cmVmbGVjdGFuY2UpKSArCiAgZ2VvbV9ib3hwbG90KGFlcyhmaWxsPXNlYXNvbikpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xzKSArCiAgZmFjZXRfd3JhcCguIH4gYmFuZCwgc2NhbGVzID0gImZyZWUiKSArCiAgdGhlbWVfYncoMTIpICsKICBsYWJzKGZpbGw9IlNlYXNvbiIseT0iIikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoc2l6ZT0xMSxmYWNlPSJpdGFsaWMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXJnaW4gPSB1bml0KGMoMCwwLjQsMCwwKSwgImNtIikpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChzaXplPTExLGZhY2U9Iml0YWxpYyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1hcmdpbiA9IHVuaXQoYygwLjQsMCwwLDApLCAiY20iKSksCiAgICAgICAgYXhpcy50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemU9MTApLAogICAgICAgIHN0cmlwLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTEpLAogICAgICAgICMgbGVnZW5kLnBvc2l0aW9uID0gYygwLjg1LDAuMiksCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICAgICAgbGVnZW5kLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgICAgIGxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgICAgIGxlZ2VuZC5zcGFjaW5nLnkgPSB1bml0KDEuNSwgImNtIiksCiAgICAgICAgbGVnZW5kLnNwYWNpbmcueCA9IHVuaXQoMC41LCAnY20nKSwKICAgICAgICBwbG90Lm1hcmdpbiA9IHVuaXQoYygxLjIsMC41LDEuMiwwLjUpLCAnbGluZXMnKSwKICAgICAgICB0ZXh0ID0gZWxlbWVudF90ZXh0KGZhbWlseSA9ICJBcmlhbCIpKQpmM2QKCmBgYAoKQ29tYmluZSB0aGUgcGxvdHM6CgpgYGB7ciBmaWcud2lkdGg9Ny41LCBmaWcuaGVpZ2h0PTEwLCBtZXNzYWdlPUYsIHdhcm5pbmc9Rn0KCiMgQXJyYW5nZSBpbiBhIG11bHRpLXBhbmVsIHBsb3QKCmYzIDwtIGdnYXJyYW5nZShmM2EsZjNiLGYzYyxmM2QsIG5yb3c9MixuY29sPTIsIHdpZHRocz1jKDEsIDAuNzUpLCBsYWJlbHMgPSBjKCJBIiwgIkIiLCAiQyIsICJEIikpCmYzCgojIFNhdmUgaXQgb3V0CgpnZ3NhdmUoZjMsIGZpbGUgPSAiLi4vLi4vZmlndXJlcy9GaWd1cmUzX1NwZWN0cmFsUmVzcG9uc2UucG5nIiwKICAgICAgIGRwaSA9IDMwMCwgYmc9IndoaXRlIikgIyBhZGp1c3QgZHBpIGFjY29yZGluZ2x5CgpgYGAKClZlcnNpb24gMjoKCmBgYHtyIGZpZy53aWR0aD0xNCwgZmlnLmhlaWdodD04fQoKZjNfIDwtIGdnYXJyYW5nZShmM2EsZ2dhcnJhbmdlKGYzYyxmM2QsbnJvdz0xLG5jb2w9MiksCiAgICAgICAgICAgICAgICAgIG5jb2w9MSx3aWR0aHM9YygxLCAwLjc1KSwKICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygiQSIsICJCIiwgIkMiKSkKZjNfCgpnZ3NhdmUoZjNfLCBmaWxlID0gIi4uLy4uL2ZpZ3VyZXMvRmlndXJlM19TcGVjdHJhbFJlc3BvbnNlX3YyLnBuZyIsCiAgICAgICBkcGkgPSAzMDAsIGJnPSJ3aGl0ZSIpICMgYWRqdXN0IGRwaSBhY2NvcmRpbmdseQoKYGBgCgpDbGVhbiB1cCB0aGUgdGltZS1zZXJpZXMgLyBzcGVjdHJhbCByZXNwb25zZSBkYXRhOgoKYGBge3J9CnJtKGYzLGYzYSxmM2IsZjNjLGYzZCxzZWFzb25hbCxiYW5kX2NvbG9ycyxjb2xzLGVuZF9hdXR1bW4sZW5kX3N1bW1lcixzdGFydF9hdXR1bW4sc3RhcnRfc3VtbWVyLHRzcF8pCmdjKCkKYGBgCgojIE1vZGVsIFNlbGVjdGlvbiBhbmQgQWNjdXJhY3kgQXNzZXNzbWVudAoKTG9hZCB0aGUgYWNjdXJhY3kgYXNzZXNzbWVudCByZXN1bHRzIGZvciB0aGUgYmVzdCBwZXJmb3JtaW5nIG1vZGVsICh0aGUgZmluYWwgbW9kZWwgaW4gR0VFKS4gVGhpcyByZXByZXNlbnRzIHRoZSBtb3N0IHBhcnNpbW9uaW91cyBtb2RlbCAobXVsdGljb2xsaW5lYXIgYmFuZHMgcmVtb3ZlZCBhbmQgZmVhdHVyZSBzZWxlY3Rpb24gaW4gcmZVdGlsaXRpZXMpLiBTZWUgdGhlICJhY2NtZWFzLlIiIHNjcmlwdC4KCmBgYHtyfQphY2NtZWFzIDwtIHJlYWRfY3N2KCcuLi8uLi9kYXRhL3RhYnVsYXIvbW9kL3Jlc3VsdHMvYmVzdF9tb2RlbC9zb3V0aGVybl9yb2NraWVzX2FjY21lYXMuY3N2JywKICAgICAgICAgICAgICAgICAgICBzaG93X2NvbF90eXBlcyA9IEZBTFNFKQpvcHRfdGhyZXNoIDwtIHJlYWRfY3N2KCcuLi8uLi9kYXRhL3RhYnVsYXIvbW9kL3Jlc3VsdHMvYmVzdF9tb2RlbC9zb3V0aGVybl9yb2NraWVzX29wdF90aHJlc2guY3N2JywKICAgICAgICAgICAgICAgICAgICBzaG93X2NvbF90eXBlcyA9IEZBTFNFKQoKZ2xpbXBzZShhY2NtZWFzKQpoZWFkKG9wdF90aHJlc2gpCgpgYGAKCiMjIEZpZ3VyZSAzOiBDbGFzc2lmaWNhdGlvbiBTY2VuYXJpb3MKCmBgYHtyIGZpZy53aWR0aD02LjUsIGZpZy5oZWlnaHQ9My41LCBtZXNzYWdlPUYsIHdhcm5pbmc9Rn0KCnNjZW5hcmlvcyA8LSByZWFkX2NzdigiLi4vLi4vZGF0YS90YWJ1bGFyL21vZC9yZXN1bHRzL3NjZW5hcmlvcy9yZl9hY2NtZXRzX3NjZW5hcmlvcy5jc3YiLCBzaG93X2NvbF90eXBlcyA9IEZBTFNFKQoKIyBSZWFkIGluIHRoZSBiZXN0IHBlcmZvcm1pbmcgbW9kZWwgcmVzdWx0cyB0byBhcHBlbmQKZmluYWxfbW9kZWwgPC0gb3B0X3RocmVzaCAlPiUKICBtdXRhdGUoRmVhdHVyZV9TZXQgPSAiRmluYWxfTW9kZWwiKSAlPiUKICByZW5hbWUoRjEgPSBmMSkgJT4lCiAgc2VsZWN0KEZlYXR1cmVfU2V0LEYxKQoKc2NlbmFyaW9zIDwtIGJpbmRfcm93cyhzY2VuYXJpb3MsZmluYWxfbW9kZWwpCmhlYWQoc2NlbmFyaW9zKQoKIyBCb3ggcGxvdCB2ZXJzaW9uCmdncGxvdChkYXRhPXNjZW5hcmlvcywgYWVzKHg9cmVvcmRlcihGZWF0dXJlX1NldCxGMSkseT1GMSkpICsKICBnZW9tX2JveHBsb3Qob3V0bGllci5zaXplPTAuNiwgZmlsbD0ibGlnaHRibHVlIikgKwogIHlsaW0oMCwxKSArCiAgbGFicyh4PSJDbGFzc2lmaWNhdGlvbiBTY2VuYXJpbyIseT0iRjEtc2NvcmUiLCB0YWc9IkEiKSArCiAgdGhlbWVfYncoMTApICsKICB0aGVtZSgKICAgICAgICBwbG90Lm1hcmdpbiA9IHVuaXQoYygwLjIsMC4yLDAuMiwwLjIpLCAnbGluZXMnKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoc2l6ZT0xMCxmYWNlPSJpdGFsaWMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXJnaW4gPSB1bml0KGMoMCwwLjQsMCwwKSwgImNtIikpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChzaXplPTEwLGZhY2U9Iml0YWxpYyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1hcmdpbiA9IHVuaXQoYygwLjQsMCwwLDApLCAiY20iKSksCiAgICAgICAgYXhpcy50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemU9OSksCiAgICAgICAgc3RyaXAudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3Q9MSksCiAgICAgICAgdGV4dCA9IGVsZW1lbnRfdGV4dChmYW1pbHkgPSAiQXJpYWwiKSkKCiMgQ2FsY3VsYXRpbmcgdGhlIG1lYW5zIGFuZCBzdGFuZGFyZCBlcnJvcnMgZm9yIEYxLXNjb3JlcwojIFBsb3QgYXMgYSBiYXIgY2hhcnQgd2l0aCBzdGFuZGFyZCBlcnJvcnMKZjNhIDwtIHNjZW5hcmlvcyAlPiUKICBncm91cF9ieShGZWF0dXJlX1NldCkgJT4lCiAgc3VtbWFyaXNlKAogICAgTWVhbiA9IG1lYW4oRjEpLAogICAgU0UgPSBzZChGMSkgLyBzcXJ0KG4oKSkKICApICU+JQogICMgUGxvdHRpbmcgdGhlIGJhciBjaGFydCB3aXRoIGVycm9yIGJhcnMKICBnZ3Bsb3QoYWVzKHg9cmVvcmRlcihGZWF0dXJlX1NldCwgTWVhbiksIHk9TWVhbikpICsKICAgIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IiwgZmlsbD0ibGlnaHRibHVlIiwgd2lkdGg9MC43NSkgKwogICAgZ2VvbV9lcnJvcmJhcihhZXMoeW1pbj1NZWFuLVNFLCB5bWF4PU1lYW4rU0UpLCB3aWR0aD0wLjIpICsKICAgIHlsaW0oMCwgMSkgKwogICAgbGFicyh4PSJDbGFzc2lmaWNhdGlvbiBTY2VuYXJpbyIsIHk9IkYxLXNjb3JlIikgKwogICAgdGhlbWVfYncoYmFzZV9zaXplID0gMTApICsKICAgIHRoZW1lKAogICAgICBwbG90Lm1hcmdpbiA9IHVuaXQoYygwLjIsMC4yLDAuMiwwLjIpLCAnbGluZXMnKSwKICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemU9MTAsIGZhY2U9Iml0YWxpYyIsIG1hcmdpbiA9IHVuaXQoYygwLDAuNCwwLDApLCAiY20iKSksCiAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChzaXplPTEwLCBmYWNlPSJpdGFsaWMiLCBtYXJnaW4gPSB1bml0KGMoMC40LDAsMCwwKSwgImNtIikpLAogICAgICBheGlzLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT05KSwKICAgICAgc3RyaXAudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksCiAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0PTEpLAogICAgICB0ZXh0ID0gZWxlbWVudF90ZXh0KGZhbWlseSA9ICJBcmlhbCIpCiAgICApCmYzYQoKIyBTYXZlIG91dApnZ3NhdmUoZjNhLCBmaWxlID0gIi4uLy4uL2ZpZ3VyZXMvRmlndXJlNEFfQ2xhc3NfU2NlbmFyaW9zLnBuZyIsCiAgICAgICBkcGkgPSAzMDAsIGJnPSJ3aGl0ZSIpICMgYWRqdXN0IGRwaSBhY2NvcmRpbmdseQoKYGBgCgojIyBUYWJsZSBYLiBBdmVyYWdlIEYxLXNjb3JlIGFuZCBvdGhlciBtZXRyaWNzIGZvciB0aGUgY2xhc3NpZmljYXRpb24gc2NlbmFyaW9zIGFuZCB0aGUgZmluYWwgbW9kZWwKCmBgYHtyfQojIFJlYWQgaW4gdGhlIGJlc3QgcGVyZm9ybWluZyBtb2RlbCByZXN1bHRzIHRvIGFwcGVuZApmaW5hbF9tb2RlbCA8LSBvcHRfdGhyZXNoICU+JQogIG11dGF0ZShGZWF0dXJlX1NldCA9ICJGaW5hbF9Nb2RlbCIpICU+JQogIHJlbmFtZShGMSA9IGYxKSAlPiUKICBzZWxlY3QoRmVhdHVyZV9TZXQsRjEpCgojIEFkZCB0aGUgZmluYWwgbW9kZWwgdG8gdGhlIGRhdGEgZnJhbWUKc2NlbmFyaW9zXyA8LSBiaW5kX3Jvd3Moc2NlbmFyaW9zLGZpbmFsX21vZGVsKQoKIyBDcmVhdGUgdGhlIGZpbmFsIHRhYmxlCmRmIDwtIHNjZW5hcmlvc18gJT4lCiAgZ3JvdXBfYnkoRmVhdHVyZV9TZXQpICU+JQogIHN1bW1hcml6ZSgKICAgIEYxX21uID0gbWVhbihGMSksCiAgICBGMV9zZCA9IHNkKEYxKQogICkgJT4lCiAgbXV0YXRlKAogICAgbl9mZWF0dXJlcyA9IGlmX2Vsc2UoRmVhdHVyZV9TZXQgPT0gIlN1bW1lcl9TMSIgfCBGZWF0dXJlX1NldCA9PSAiV2ludGVyX1MxIiwgNSwgMCksCiAgICBuX2ZlYXR1cmVzID0gaWZfZWxzZShGZWF0dXJlX1NldCA9PSAiU3VtbWVyX1MxX0dMQ00iIHwgRmVhdHVyZV9TZXQgPT0gIldpbnRlcl9TMV9HTENNIiwgMTMsIG5fZmVhdHVyZXMpLAogICAgbl9mZWF0dXJlcyA9IGlmX2Vsc2UoRmVhdHVyZV9TZXQgPT0gIlN1bW1lcl9XaW50ZXJfUzEiLCAyMywgbl9mZWF0dXJlcyksCiAgICBuX2ZlYXR1cmVzID0gaWZfZWxzZShGZWF0dXJlX1NldCA9PSAiU3VtbWVyX1MyIiB8IEZlYXR1cmVfU2V0ID09ICJBdXR1bW5fUzIiLCAxMywgbl9mZWF0dXJlcyksCiAgICBuX2ZlYXR1cmVzID0gaWZfZWxzZShGZWF0dXJlX1NldCA9PSAiU3VtbWVyX1MyX1ZJIiB8IEZlYXR1cmVfU2V0ID09ICJBdXR1bW5fUzJfVkkiLCAxOSwgbl9mZWF0dXJlcyksCiAgICBuX2ZlYXR1cmVzID0gaWZfZWxzZShGZWF0dXJlX1NldCA9PSAiU3VtbWVyX0F1dHVtbl9TMiIsIDM1LCBuX2ZlYXR1cmVzKSwKICAgIG5fZmVhdHVyZXMgPSBpZl9lbHNlKEZlYXR1cmVfU2V0ID09ICJDb21iaW5lZF9TMV9TMiIsIDU1LCBuX2ZlYXR1cmVzKSwKICAgIG5fZmVhdHVyZXMgPSBpZl9lbHNlKEZlYXR1cmVfU2V0ID09ICJGaW5hbF9Nb2RlbCIsIDE3LCBuX2ZlYXR1cmVzKQogICkKCiMgQ3JlYXRlIGEgdGlkeSB0YWJsZQoKIyBTZXQgZm9udCBuYW1lIGZvciB0YWJsZQpmb250bmFtZSA8LSAiVGltZXMgTmV3IFJvbWFuIgoKIyBDbGVhbiB1cCBleHRyYSByb3dzIGFuZCBkaWdpdHMKY2xlYW5lZCA8LSBkZiAlPiUKIG11dGF0ZV9pZihpcy5kb3VibGUsIH4gcm91bmQoLiwgZGlnaXRzID0gNCkpICU+JQogIyBTb3J0IGZyb20gZmFzdGVzdCB0byBzbG93ZXN0CiBhcnJhbmdlKGRlc2MoRjFfbW4pKQoKIyBGaXggbmFtZXMgYW5kIGFkZCB1bml0cwpuYW1lcyhjbGVhbmVkKSA8LSBjKCJGZWF0dXJlIFNldCIsIk1lYW4gRjEtc2NvcmUiLCJTdGFuZGFyZCBEZXZpYXRpb24iLCJOdW1iZXIgb2YgRmVhdHVyZXMiKQoKIyBDcmVhdGUgdGhlIGZsZXh0YWJsZQpmdCA8LSBmbGV4dGFibGU6OmZsZXh0YWJsZShjbGVhbmVkKSAlPiUKICBmbGV4dGFibGU6OmZvbnQoZm9udG5hbWUgPSBmb250bmFtZSwgcGFydCA9ICJhbGwiKSAlPiUKICBmbGV4dGFibGU6OmF1dG9maXQoKSAlPiUgCiAgZmxleHRhYmxlOjpmaXRfdG9fd2lkdGgoNy41KQpmdAojIHByaW50KGZ0LCBwcmV2aWV3ID0gImRvY3giKQoKcm0oc2NlbmFyaW9zXyxkZixmdCxmb250bmFtZSxjbGVhbmVkKQpgYGAKCiMjIEZpZ3VyZSBTMi4gTW9kZWwgUGVyZm9ybWFuY2UgTWV0cmljczsgQVVDLVBSLCBPcHRpbXVtIEN1dG9mZgoKVGhpcyB3aWxsIGdvIGludG8gdGhlIHN1cHBsZW1lbnQuCgpUaGUgb3B0aW11bSBjdXRvZmYgdmFsdWUgZm9yIGNsYXNzaWZpY2F0aW9uIGlzIGRlZmluZWQgYXMgdGhlIGF2ZXJhZ2UgY3V0b2ZmIGFjcm9zcyBmb2xkcyBhdCB3aGljaCB0aGUgRjEtc2NvcmUgaXMgbWF4aW1pemVkLiBXZSBjYW4gc2VlIHRoZSBvcHRpbXVtIGN1dG9mZiB2YWx1ZSBhbmQgaG93IGl0IGNvcnJlc3BvbmRzIHdpdGggRjEtc2NvcmUgYW5kIHRoZSBBVUMtUFIgY3VydmUuCgpgYGB7ciB3YXJuaW5nPUYsIGZpZy53aWR0aD02LjUsIGZpZy5oZWlnaHQ9M30KcGFzdGUwKCJPcHRpbXVtIGN1dG9mZiBmb3IgY2xhc3NpZmljYXRpb246ICIscm91bmQoKG9wdF9jdXRvZmYgPC0gbWVhbihvcHRfdGhyZXNoJGN1dG9mZl9mMSkpLDMpKQpwYXN0ZTAoIkF2ZXJhZ2UgRjEtc2NvcmU6ICIscm91bmQoKGYxbWF4IDwtIG1heChvcHRfdGhyZXNoJGYxKSksMykpCgojIEFVQy1QUiBDdXJ2ZSAoYXBwcm94aW1hdGlvbikKCmZzMmEgPC0gZ2dwbG90KGRhdGE9YWNjbWVhcykgKwogIGdlb21fbGluZShhZXMoeD1mcHIseT10cHIsY29sb3I9ZmFjdG9yKG1vZGVsKSksbGluZXdpZHRoPTAuNCkgKwogIHNjYWxlX2NvbG9yX3ZpcmlkaXNfZChvcHRpb249InR1cmJvIikgKwogIHNjYWxlX3hfY29udGludW91cyhsaW1pdHM9YygwLDEpKSArICAjIFNldCB4IGF4aXMgbGltaXRzIGZyb20gMCB0byAxCiAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cz1jKDAsMSkpICsKICBsYWJzKHg9J0ZhbHNlIFBvc2l0aXZlIFJhdGUnLCB5PSdUcnVlIFBvc2l0aXZlIFJhdGUnLCB0YWc9IkEiKSArCiAgY29vcmRfZml4ZWQocmF0aW8gPSAxKSArICAjIHRvIGtlZXAgdGhlIHggYW5kIHkgYXhlcyBzY2FsZXMgc2FtZQogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDAsIHNsb3BlID0gMSwgbGluZXR5cGUgPSAiZGFzaGVkIiwgY29sb3IgPSAiYmxhY2siKSArICAjIGRpYWdvbmFsCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLAogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChzaXplPTExLGZhY2U9Iml0YWxpYyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1hcmdpbiA9IHVuaXQoYygwLDAuNCwwLDApLCAiY20iKSksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF90ZXh0KHNpemU9MTEsZmFjZT0iaXRhbGljIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWFyZ2luID0gdW5pdChjKDAuNCwwLDAsMCksICJjbSIpKSwKICAgICAgICBheGlzLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT0xMCksCiAgICAgICAgc3RyaXAudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMSkpCgpmczJiIDwtIGdncGxvdChkYXRhPWFjY21lYXMpICsKICBnZW9tX2xpbmUoYWVzKHg9Y3V0b2ZmLHk9ZjEsY29sb3I9ZmFjdG9yKG1vZGVsKSksbGluZXdpZHRoPTAuNCkgKwogIHNjYWxlX2NvbG9yX3ZpcmlkaXNfZChvcHRpb249InR1cmJvIikgKwogICMgZ2VvbV92bGluZSh4aW50ZXJjZXB0PTAuNTAsIGxpbmV0eXBlPSJkYXNoZWQiLCBjb2xvcj0iYmxhY2siKSArCiAgZ2VvbV9wb2ludChhZXMoeD1vcHRfY3V0b2ZmLCB5PWYxbWF4KSwKICAgICAgICAgICAgIGNvbG9yID0gImJsYWNrIiwgc2l6ZSA9IDMsIHNoYXBlID0gMTkpICsKICBzY2FsZV94X2NvbnRpbnVvdXMobGltaXRzPWMoMCwxKSkgKyAgIyBTZXQgeCBheGlzIGxpbWl0cyBmcm9tIDAgdG8gMQogIHNjYWxlX3lfY29udGludW91cyhsaW1pdHM9YygwLDEpKSArCiAgbGFicyh4PSdDbGFzc2lmaWNhdGlvbiBUaHJlc2hvbGQnLCB5PSdGMSBTY29yZScsIHRhZz0iQiIpICsKICBnZW9tX3RleHQoYWVzKHg9b3B0X2N1dG9mZiwgeT1mMW1heCksCiAgICAgICAgICAgIGxhYmVsID0gcGFzdGUwKCdPcHRpbWFsIHRocmVzaG9sZDogJywgcm91bmQob3B0X2N1dG9mZiwzKSksCiAgICAgICAgICAgIG51ZGdlX3ggPSAwLjAsIG51ZGdlX3kgPSAwLjA1LCBzaXplID0gMy41KSArCiAgY29vcmRfZml4ZWQocmF0aW8gPSAxKSArICAjIHRvIGtlZXAgdGhlIHggYW5kIHkgYXhlcyBzY2FsZXMgc2FtZQogIHRoZW1lX2xpZ2h0KCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoc2l6ZT0xMSxmYWNlPSJpdGFsaWMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXJnaW4gPSB1bml0KGMoMCwwLjQsMCwwKSwgImNtIikpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChzaXplPTExLGZhY2U9Iml0YWxpYyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1hcmdpbiA9IHVuaXQoYygwLjQsMCwwLDApLCAiY20iKSksCiAgICAgICAgYXhpcy50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemU9MTApLAogICAgICAgIHN0cmlwLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTEpKQoKIyBBcnJhbmdlCihmczIgPC0gZ2dhcnJhbmdlKGZzMmEsZnMyYikpCgojIFNhdmUgb3V0Cmdnc2F2ZShmczIsIGZpbGUgPSAiLi4vLi4vZmlndXJlcy9GaWd1cmVTMl9Nb2RlbF9QZXJmb3JtYW5jZS5wbmciLAogICAgICAgZHBpID0gMzAwLCBiZz0id2hpdGUiKSAjIGFkanVzdCBkcGkgYWNjb3JkaW5nbHkKCnJtKGZzMmEsZnMyYixmczIpCgpgYGAKClVzaW5nIHRoZSBvcHRpbXVtIGN1dG9mZiB2YWx1ZSwgd2UgY2FuIGNsYXNzaWZ5IHRoZSB0ZXN0IGRhdGEgYW5kIGNhbGN1bGF0ZSB0aGUgUHJlY2lzaW9uLCBSZWNhbGwsIGFuZCBGMS1zY29yZS4gCgpgYGB7cn0KdGVzdFBhcnQgPC0gIi4uLy4uL2RhdGEvdGFidWxhci9tb2QvcmVzdWx0cy9iZXN0X21vZGVsL3NvdXRoZXJuX3JvY2tpZXNfdGVzdF9wcm9ic19uNTUuY3N2IgojIEFzc2lnbiB0aGUgY2xhc3NpZmljYXRpb24gbGFiZWwgYmFzZWQgb24gdGhlIG9wdGltdW0gY3V0b2ZmCnRlc3REYXRhIDwtIHJlYWRfY3N2KHRlc3RQYXJ0LCBzaG93X2NvbF90eXBlcz1GQUxTRSkgJT4lCiAgcmVuYW1lKGdlZV9pZCA9IGBzeXN0ZW06aW5kZXhgLAogICAgICAgICBUcnVlTGFiZWwgPSBsYWJlbCkgJT4lCiAgZHBseXI6OnNlbGVjdCgtLmdlbykgJT4lCiAgbXV0YXRlKHByb2JhYmlsaXR5ID0gcHJvYmFiaWxpdHkqMC4wMDEsICMgc2NhbGUgYmFjawogICAgICAgICBDbGFzc0xhYmVsID0gaWZfZWxzZShwcm9iYWJpbGl0eSA+PSBvcHRfY3V0b2ZmLCAxLCAwKSkgJT4lCiAgZ3JvdXBfYnkoc2VlZCkgJT4lCiAgc3VtbWFyaXplKAogICAgdHAgPSBzdW0oVHJ1ZUxhYmVsID09IDEgJiBDbGFzc0xhYmVsID09IDEpLAogICAgdG4gPSBzdW0oVHJ1ZUxhYmVsID09IDAgJiBDbGFzc0xhYmVsID09IDApLAogICAgZnAgPSBzdW0oVHJ1ZUxhYmVsID09IDAgJiBDbGFzc0xhYmVsID09IDEpLAogICAgZm4gPSBzdW0oVHJ1ZUxhYmVsID09IDEgJiBDbGFzc0xhYmVsID09IDApLAogICAgYWNjdXJhY3kgPSAodHAgKyB0bikgLyAodHAgKyB0biArIGZwICsgZm4pLAogICAgcHJlY2lzaW9uID0gdHAgLyAodHAgKyBmcCksCiAgICByZWNhbGwgPSB0cCAvICh0cCArIGZuKSwKICAgIGYxID0gMiAqIChwcmVjaXNpb24gKiByZWNhbGwpIC8gKHByZWNpc2lvbiArIHJlY2FsbCksCiAgICAuZ3JvdXBzID0gJ2Ryb3AnICMgVGhpcyBlbnN1cmVzIHRoYXQgdGhlIHJlc3VsdCBpcyBhIHNpbmdsZSBkYXRhIGZyYW1lLCBub3QgYSBncm91cGVkIG9uZQogICkKaGVhZCh0ZXN0RGF0YSwxMCkKCiMgRmlyc3QsIGdldCB0aGUgb3ZlcmFsbCBGMS9NQ0MgZm9yIHRoZSBjbGFzc2lmaWNhdGlvbgpwcmludCgifn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn4iKQpwYXN0ZTAoIkF2ZXJhZ2UgRjEtc2NvcmUgZm9yIHRoZSBiZXN0IHBlcmZvcm1pbmcgbW9kZWwgYXQgdGhlIG9wdGltdW0gY3V0b2ZmOiAiLG1lYW4odGVzdERhdGEkZjEpKQpwYXN0ZTAoIlJhbmdlIG9mIEYxLXNjb3JlczogIixyYW5nZSh0ZXN0RGF0YSRmMSkpCnBhc3RlMCgiU3REZXYgb2YgRjEtc2NvcmVzOiAiLHNkKHRlc3REYXRhJGYxKSkKc3VtbWFyeSh0ZXN0RGF0YSRmMSkKCiMgcm0odGVzdFBhcnQsdGVzdERhdGEpCmBgYAoKUGxvdCB0aGUgbW9kZWwgYWNjdXJhY3kgcmVzdWx0cyBmb3IgdGhlIG9wdGltdW0gdGhyZXNob2xkLgoKRmlndXJlIDRBOiBNb2RlbCBBY2N1cmFjeSAoRjEgYW5kIE9BKQoKYGBge3IgbWVzc2FnZT1GLCB3YXJuaW5nPUYsIGZpZy5oZWlnaHQ9My41LCBmaWcud2lkdGg9Ni41fQoKY29scyA8LSBjKCJGMSBTY29yZSI9IiMxZjc4YjQiLCJPdmVyYWxsIEFjY3VyYWN5Ij0iZ3JheTc1IikKCnRlc3REYXRhIDwtIHRlc3REYXRhICU+JQogIG11dGF0ZShGb2xkID0gYXMubnVtZXJpYyhmYWN0b3Ioc2VlZCwgbGV2ZWxzID0gc29ydCh1bmlxdWUoc2VlZCkpKSkpCgpmNGIgPC0gZ2dwbG90KGRhdGE9dGVzdERhdGEpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQ9bWVhbih0ZXN0RGF0YSRwcmVjaXNpb24pLGxpbmV0eXBlPSJkYXNoZWQiLGNvbG9yPSJncmF5ODUiKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0PW1lYW4odGVzdERhdGEkcmVjYWxsKSxsaW5ldHlwZT0iZGFzaGVkIixjb2xvcj0iZ3JheTQ1IikgKwogIGdlb21faGxpbmUoeWludGVyY2VwdD1tZWFuKHRlc3REYXRhJGYxKSxsaW5ldHlwZT0iZGFzaGVkIixjb2xvcj0iIzFmNzhiNCIpICsKICAjIFBsb3QgdGhlIFByZWNpc2lvbiBhbmQgUmVjYWxsCiAgZ2VvbV9saW5lKGFlcyh4PWZhY3RvcihGb2xkKSx5PXByZWNpc2lvbixncm91cD0xKSxjb2xvcj0iZ3JheTY1IikgKwogIGdlb21fbGluZShhZXMoeD1mYWN0b3IoRm9sZCkseT1yZWNhbGwsZ3JvdXA9MSksY29sb3I9ImdyYXkzNSIpICsKICAjIFBsb3QgdGhlIEYxLXNjb3JlCiAgZ2VvbV9saW5lKGFlcyh4PWZhY3RvcihGb2xkKSx5PWYxLGdyb3VwPTEpLGNvbG9yPSIjMWY3OGI0IikgKwogIGdlb21fcG9pbnQoYWVzKHg9ZmFjdG9yKEZvbGQpLHk9ZjEsc2l6ZT1mMSksIGNvbG9yPSIjMWY3OGI0IikgKwogIHNjYWxlX3NpemUocmFuZ2UgPSBjKDMsNikpICsKICBjb29yZF9jYXJ0ZXNpYW4oeWxpbSA9IGMoMC44NSwgMSkpICsKICBsYWJzKHg9IkZvbGQiLCB5PSJTY29yZSIsIHRhZz0iQiIpICsKICAjIGFubm90YXRlKCJ0ZXh0IiwgeCA9IDIuNiwgeSA9IDAuODgsIGxhYmVsID0gcGFzdGUoIkF2ZXJhZ2UgRjEgU2NvcmU6ICIsIHJvdW5kKG1lYW4odGVzdERhdGEkZjEpLCAyKSkpICsKICB0aGVtZV9idygpICsKICBndWlkZXMoc2l6ZT0ibm9uZSIpICsKICB0aGVtZSgKICAgICAgICBwbG90Lm1hcmdpbiA9IHVuaXQoYygwLjIsMC4yLDAuMiwwLjIpLCAnbGluZXMnKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoc2l6ZT0xMSxmYWNlPSJpdGFsaWMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXJnaW4gPSB1bml0KGMoMCwwLjQsMCwwKSwgImNtIikpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChzaXplPTExLGZhY2U9Iml0YWxpYyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1hcmdpbiA9IHVuaXQoYygwLjQsMCwwLDApLCAiY20iKSksCiAgICAgICAgYXhpcy50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemU9MTApLAogICAgICAgIHN0cmlwLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTEpLAogICAgICAgIHRleHQgPSBlbGVtZW50X3RleHQoZmFtaWx5ID0gIkFyaWFsIikpCmY0YgoKZ2dzYXZlKGY0YiwgZmlsZSA9ICIuLi8uLi9maWd1cmVzL0ZpZ3VyZTRBX0Jlc3RfTW9kZWxfQWNjX0YxLnBuZyIsCiAgICAgICBkcGkgPSAzMDAsIGJnPSJ3aGl0ZSIpICMgYWRqdXN0IGRwaSBhY2NvcmRpbmdseQoKYGBgCgpgYGB7ciBmaWcud2lkdGg9Ny41LCBmaWcuaGVpZ2h0PTcuNX0KYXJyNCA8LSBnZ2FycmFuZ2UoZjRhLGdnYXJyYW5nZShmNGIsZjViLCBuY29sPTIsIG5yb3c9MSwgYWxpZ249J2gnKSwgbnJvdz0yLCBhbGlnbj0iaCIpCmFycjQKZ2dzYXZlKGFycjQsIGZpbGUgPSAiLi4vLi4vZmlndXJlcy9GaWd1cmU0X01vZGVsU2VsX0Jlc3RNb2RlbF9UaHJlc2hPcHQucG5nIiwKICAgICAgIGRwaSA9IDMwMCwgYmc9IndoaXRlIikgIyBhZGp1c3QgZHBpIGFjY29yZGluZ2x5CmBgYAoKIyMgRmlndXJlIDY6IEZlYXR1cmUgaW1wb3J0YW5jZSBmcm9tIHRoZSBiZXN0IG1vZGVsCgpgYGB7cn0KIyBGZWF0dXJlIEltcG9ydGFuY2UKZnRyX2ltcCA8LSByZWFkX2NzdignLi4vLi4vZGF0YS90YWJ1bGFyL21vZC9yZXN1bHRzL2Jlc3RfbW9kZWwvc291dGhlcm5fcm9ja2llc19mZWF0dXJlX2ltcHNfbjU1LmNzdicsCiAgICAgICAgICAgICAgICAgICAgc2hvd19jb2xfdHlwZXMgPSBGQUxTRSkKZ2xpbXBzZShmdHJfaW1wKQpgYGAKCmBgYHtyIG1lc3NhZ2U9Riwgd2FybmluZz1GLCBmaWcud2lkdGg9IDYuNSwgZmlnLmhlaWdodD0zLjV9CgojIFRpZHkgdGhlIGRhdGEgZnJhbWUKZGYuaW1wIDwtIGZ0cl9pbXAgJT4lIAogIHNlbGVjdCgtYyguZ2VvLGBzeXN0ZW06aW5kZXhgKSkgJT4lCiAgcmVuYW1lKG1vZGVsID0gc2VlZCkgJT4lCiAgbXV0YXRlKG1vZGVsID0gYXMuZmFjdG9yKG1vZGVsKSkKCiMgUGl2b3QgbG9uZ2VyCmRmLmltcC5wIDwtIGRmLmltcCAlPiUKICBwaXZvdF9sb25nZXIoY29scyA9IC1tb2RlbCkgJT4lCiAgcmVuYW1lKGltcG9ydGFuY2UgPSB2YWx1ZSwKICAgICAgICAgYmFuZCA9IG5hbWUpICU+JQogIG11dGF0ZShzZWFzb24gPSBpZl9lbHNlKHN0cl9kZXRlY3QoYmFuZCwiX2F1dHVtbiIpLCAiQXV0dW1uIFMyIiwgIlN1bW1lciBTMiIpLAogICAgICAgICBzZWFzb24gPSBpZl9lbHNlKHN0cl9kZXRlY3QoYmFuZCwiX3dpbnRlciIpLCAiV2ludGVyIFMxIiwgc2Vhc29uKSwKICAgICAgICAgc2Vhc29uID0gaWZfZWxzZShzdHJfZGV0ZWN0KGJhbmQsIlZWX3N1bW1lciIpLCAiU3VtbWVyIFMxIiwgc2Vhc29uKSwKICAgICAgICAgc2Vhc29uID0gaWZfZWxzZShzdHJfZGV0ZWN0KGJhbmQsIlZIX3N1bW1lciIpLCAiU3VtbWVyIFMxIiwgc2Vhc29uKSkKZ2xpbXBzZShkZi5pbXAucCkKCiMgR3JhYiB0aGUgdG9wIDIwIG92ZXIgYWxsIG1vZGVsIHJ1bnMKdG9wIDwtIGRmLmltcC5wICU+JQogIGdyb3VwX2J5KGJhbmQpICU+JQogIHN1bW1hcml6ZShtZWRpYW4gPSBtZWRpYW4oaW1wb3J0YW5jZSkpICU+JQogIHVuZ3JvdXAoKQp0b3AgPC0gaGVhZChhcnJhbmdlKHRvcCxkZXNjKG1lZGlhbikpLCBuID0gMTApCgojIENvbG9yIHBhbGV0dGUKCmNvbHMgPC0gYygKICAiU3VtbWVyIFMyIiA9ICIjODdDRUVCIiwgCiAgIkF1dHVtbiBTMiIgPSAiI0RBQTUyMCIsCiAgIldpbnRlciBTMSIgPSAiZ3JheTI5IiwKICAiU3VtbWVyIFMxIiA9ICJncmF5ODkiCikKCiMgQm94cGxvdAoKIyAlPiUgZmlsdGVyKGJhbmQgJWluJSB0b3AkYmFuZCkKZjYgPC0gZ2dwbG90KGRhdGE9ZGYuaW1wLnAgJT4lIGZpbHRlcihiYW5kICVpbiUgdG9wJGJhbmQpLCAKICAgICAgICAgICAgICBhZXMoeD1yZW9yZGVyKGJhbmQsaW1wb3J0YW5jZSksIGZpbGw9c2Vhc29uKSkgKwogIGdlb21fYm94cGxvdChhZXMoeT1pbXBvcnRhbmNlKSwgcG9zaXRpb24gPSBwb3NpdGlvbl9kb2RnZSgwLjUpKSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY29scywgCiAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygiQXV0dW1uIFMyIiwiU3VtbWVyIFMxIiwiU3VtbWVyIFMyIiwiV2ludGVyIFMxIikpICsKICBjb29yZF9mbGlwKCkgKwogIHRoZW1lX2J3KDExKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZT0xMSksCiAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoYW5nbGUgPSAwLCB2anVzdD0wLCBzaXplPTExKSwKICAgICAgICBheGlzLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9MTEsZmFjZT0iaXRhbGljIiksCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICAgICAgbGVnZW5kLmp1c3RpZmljYXRpb24gPSBjKDEuMiwwLjUpLCAjIExlZnQtYWxpZ25zIHRoZSBsZWdlbmRzCiAgICAgICAgbGVnZW5kLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT0xMSksCiAgICAgICAgdGV4dCA9IGVsZW1lbnRfdGV4dChmYW1pbHkgPSAiQXJpYWwiKSwKICAgICAgICBwbG90Lm1hcmdpbj11bml0KGMoMC41LDAuNSwwLjUsMC41KSwiY20iKSkgKwogIGxhYnMoeD0iRmVhdHVyZSIsIHk9IkltcG9ydGFuY2UiLCBmaWxsPSJTZWFzb246ICIpCmY2CgpnZ3NhdmUoZjYsIGZpbGUgPSAiLi4vLi4vZmlndXJlcy9GaWd1cmU2X0ZlYXR1cmVJbXBvcnRhbmNlX3RvcDEwLnBuZyIsCiAgICAgICBkcGk9MzAwLCBiZz0id2hpdGUiKSAjIGFkanVzdCBkcGkgYWNjb3JkaW5nbHkKCmBgYAoKQ2xlYW4gdXAhCgpgYGB7cn0Kcm0obGlzdCA9IGxzLnN0cihtb2RlID0gJ251bWVyaWMnKSkKcm0oYWNjbWVhcyxhY2NtZWFzLmJlc3QsYWNjbWVhcy5tbixhY2NtZWFzLnMsZGYuaW1wLAogICBkZi5pbXAucCxmNCxmNGEsZjRiLGY2LGZ0cl9pbXAsdG9wLGNvbHMsYXJyNCxjbGVhbmVkLAogICBkZixmNWIsZmluYWxfbW9kZWwsZnQsb3B0X3RocmVzaCxzY2VuYXJpb3Msc2NlbmFyaW9zXyx0ZXN0RGF0YSkKZ2MoKQpgYGAKCiMgU3BhdGlhbCBBZ3JlZW1lbnQgYW5kIExhbmRzY2FwZSBQYXRjaCBEeW5hbWljcwoKIyMgVGFibGUgWDogQWNjdXJhY3kgb2YgdGhlIHJlZmVyZW5jZSBkYXRhc2V0cyBiYXNlZCBvbiB0ZXN0IGRhdGEKCmBgYHtyIG1lc3NhZ2U9Rn0KcmVmX2FjYyA8LSByZWFkX2NzdignLi4vLi4vZGF0YS90YWJ1bGFyL21vZC9yZXN1bHRzL2Jlc3RfbW9kZWwvc291dGhlcm5fcm9ja2llc19yZWZfYWNjbWVhcy5jc3YnLAogICAgICAgICAgICAgICAgICAgIHNob3dfY29sX3R5cGVzID0gRikKaGVhZChyZWZfYWNjKQoKIyBDcmVhdGUgYSB0aWR5IHRhYmxlCmRmIDwtIHJlZl9hY2MgJT4lCiAgZmlsdGVyKG1ldHJpYyA9PSAiUHJlZGljdGVkIikgJT4lCiAgc2VsZWN0KHJlZmVyZW5jZSxwcmVjaXNpb24sIHJlY2FsbCwgZjFfc2NvcmUpCmhlYWQoZGYpCgojIENyZWF0ZSBhIHRpZHkgdGFibGUKCiMgU2V0IGZvbnQgbmFtZSBmb3IgdGFibGUKZm9udG5hbWUgPC0gIlRpbWVzIE5ldyBSb21hbiIKCiMgQ2xlYW4gdXAgZXh0cmEgcm93cyBhbmQgZGlnaXRzCmNsZWFuZWQgPC0gZGYgJT4lCiBtdXRhdGVfaWYoaXMuZG91YmxlLCB+IHJvdW5kKC4sIGRpZ2l0cyA9IDQpKSAlPiUKICMgU29ydCBmcm9tIGZhc3Rlc3QgdG8gc2xvd2VzdAogYXJyYW5nZShkZXNjKGYxX3Njb3JlKSkKCiMgRml4IG5hbWVzIGFuZCBhZGQgdW5pdHMKbmFtZXMoY2xlYW5lZCkgPC0gYygiRGF0YSBTb3VyY2UiLCJQcmVjaXNpb24iLCJSZWNhbGwiLCJGMS1zY29yZSIpCgojIENyZWF0ZSB0aGUgZmxleHRhYmxlCmZ0IDwtIGZsZXh0YWJsZTo6ZmxleHRhYmxlKGNsZWFuZWQpICU+JQogIGZsZXh0YWJsZTo6Zm9udChmb250bmFtZSA9IGZvbnRuYW1lLCBwYXJ0ID0gImFsbCIpICU+JQogIGZsZXh0YWJsZTo6YXV0b2ZpdCgpICU+JSAKICBmbGV4dGFibGU6OmZpdF90b193aWR0aCg3LjUpCmZ0CnByaW50KGZ0LCBwcmV2aWV3ID0gImRvY3giKQoKcm0oZGYscmVmX2FjYykKYGBgCgojIyBGaWd1cmUgNzogU3BhdGlhbCBBZ3JlZW1lbnQgKHBpeGVsLWJhc2VkKQoKYGBge3J9CmFnZ3Iuc3IgPC0gYWdnci5zciAlPiUKICBtdXRhdGUoc291cmNlID0gaWZfZWxzZShzb3VyY2UgPT0gInVzZnNfdHJlZW1hcDE2X2JhbGl2ZV9pbnRfYmluX3NybWVfMTBtIiwgIlVTRlMgVHJlZU1hcCIsIHNvdXJjZSkpCmdsaW1wc2UoYWdnci5zcikKYGBgCgpSZW1vdmUgYmxvY2tzIHdpdGggbGl0dGxlIHRvIG5vIGFzcGVuIGZvcmVzdCBjb3Zlci4gRmlyc3QgbG9vayBhdCB0aGUgZGlzdHJpYnV0aW9uIG9mIGFzcGVuIGZvcmVzdCBjb3ZlciBhY3Jvc3Mgc3BhdGlhbCBibG9ja3MuCgpgYGB7ciB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0KCiMgUGxvdCB0aGUgZGlzdHJpYnV0aW9uIG9mIGFzcGVuIGFyZWEKYmxvY2tzICU+JQogIHN0X3NldF9nZW9tZXRyeShOVUxMKSAlPiUKICBhc190aWJibGUoKSAlPiUKICBtdXRhdGUoczJhc3Blbl9zdW0gPSBhcy5pbnRlZ2VyKHMyYXNwZW5fc3VtKSkgJT4lCiAgZ2dwbG90KGFlcyh4PXMyYXNwZW5fc3VtKSkgKwogIHNjYWxlX3hfY29udGludW91cyh0cmFucz0ibG9nIikgKwogIGdlb21faGlzdG9ncmFtKCkgKwogIHRoZW1lX21pbmltYWwoMTIpCgojIEZpbHRlciBvdXQgbm9uLWFzcGVuIGJsb2NrcwpibG9ja3NfYXNwZW4gPC0gYmxvY2tzICU+JQogIGZpbHRlcihzMmFzcGVuX3N1bSA+IDEwMDAwMCkKCmBgYAoKYGBge3IgbWVzc2FnZT1GLCBmaWcud2lkdGggPSA2LjUsIGZpZy5oZWlnaHQgPSAzLjI1fQoKZmxhYnMgPC0gYyhwcmVjaXNpb24gPSAiUHJlY2lzaW9uIiwgcmVjYWxsID0gIlJlY2FsbCIsIGYxID0gIkYxLXNjb3JlIikKCiMgUmVzaGFwZSB0aGUgYWdyZWVtZW50IHRhYmxlIGZvciBwbG90dGluZyBmYWNldCB3cmFwCgojIFNvdXRoZXJuIFJvY2tpZXMgKGJ5IGJsb2NrKQphZ2dyLnNyLm0gPC0gcmVzaGFwZTI6Om1lbHQoCiAgYWdnci5zciwgaWQudmFycyA9IGMoImJsb2Nrc2l6ZSIsICJzb3VyY2UiLCAicmVnaW9uIiwgImJsb2NrIiksIAogIG1lYXN1cmUudmFycyA9IGMoInByZWNpc2lvbiIsICJyZWNhbGwiLCAiZjEiKSwgdmFyaWFibGUubmFtZSA9ICJzdGF0aXN0aWMiCikgJT4lCiAgbmEub21pdCgpICU+JQogIGZpbHRlcihibG9ja3NpemUgPT0gMSwKICAgICAgICAgYmxvY2sgJWluJSBibG9ja3NfYXNwZW4kaWQpCgojIFdoaXRlIFJpdmVyIE5GCmFnZ3Iud3IubSA8LSByZXNoYXBlMjo6bWVsdCgKICBhZ2dyLndyLCBpZC52YXJzID0gYygiYmxvY2tzaXplIiwgInNvdXJjZSIsICJyZWdpb24iKSwgCiAgbWVhc3VyZS52YXJzID0gYygicHJlY2lzaW9uIiwgInJlY2FsbCIsICJmMSIpLCB2YXJpYWJsZS5uYW1lID0gInN0YXRpc3RpYyIKKSAlPiUKICBuYS5vbWl0KCkgJT4lCiAgZmlsdGVyKGJsb2Nrc2l6ZSA9PSAxKQoKIyBGYWNldCB3cmFwIHBsb3Qgb2YgdGhlIHN0YXRpc3RpY3MgYnkgYmxvY2sgZ3JvdXAKCmY3IDwtIGdncGxvdChkYXRhPWFnZ3Iuc3IubSwgYWVzKHg9ZmFjdG9yKGJsb2Nrc2l6ZSksIHk9dmFsdWUsIGZpbGw9ZmFjdG9yKHNvdXJjZSkpKSArCiAgZ2VvbV9ib3hwbG90KHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2UoKSkgKyAKICBzY2FsZV9maWxsX3ZpcmlkaXNfZChsYWJlbHM9YygiTEFOREZJUkUgRVZUIiwiVVNGUyBUcmVlTWFwIiwiVVNGUyBJVFNQIiksIG5hbWU9IiIsCiAgICAgICAgICAgICAgICAgICAgICAgYnJlYWtzPWMoIkxBTkRGSVJFIEVWVCIsICJVU0ZTIFRyZWVNYXAiLCAiVVNGUyBJVFNQIikpICsKICBmYWNldF93cmFwKH5zdGF0aXN0aWMsIGxhYmVsbGVyID0gbGFiZWxsZXIoc3RhdGlzdGljID0gZmxhYnMpKSArCiAgdGhlbWVfYncoMTEpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgICAgICBsZWdlbmQuYm94ID0gInZlcnRpY2FsIiwKICAgICAgICBsZWdlbmQuanVzdGlmaWNhdGlvbiA9IGMoMC41LDAuNSksICMgTGVmdC1hbGlnbnMgdGhlIGxlZ2VuZHMKICAgICAgICBsZWdlbmQuc3BhY2luZy55ID0gdW5pdCgtNSwgInB0IiksICMgQWRqdXN0cyB0aGUgZ2FwIGJldHdlZW4gdGhlIGxlZ2VuZHMKICAgICAgICAjIGxlZ2VuZC50ZXh0ID0gZ2d0ZXh0OjplbGVtZW50X21hcmtkb3duKGhhbGlnbiA9IDApLCAjIFRoaXMgYWxpZ25zIGVhY2ggaW5kaXZpZHVhbCBsZWdlbmQncyB0ZXh0IHRvIHRoZSBsZWZ0CiAgICAgICAgYXhpcy50aXRsZS54PWVsZW1lbnRfYmxhbmsoKSwgICMgUmVtb3ZlIHggYXhpcyB0aXRsZQogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwgICAjIFJlbW92ZSB4IGF4aXMgdGV4dAogICAgICAgIGF4aXMudGlja3MueD1lbGVtZW50X2JsYW5rKCksICAjIFJlbW92ZSB4IGF4aXMgdGlja3MKICAgICAgICBheGlzLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9MTEsZmFjZT0iaXRhbGljIikpICsKICAgICAgICAjIHRleHQgPSBlbGVtZW50X3RleHQoZmFtaWx5ID0gIlRpbWVzIE5ldyBSb21hbiIpKSArCiAgbGFicyh4PSJCbG9ja3NpemUiLCB5PSJWYWx1ZSIpCgojIEFkZCB0aGUgV1JORiBzdGF0aXN0aWNzCmY3IDwtIGY3ICsKICBnZW9tX3BvaW50KGRhdGE9YWdnci53ci5tLCBhZXMoeD1mYWN0b3IoYmxvY2tzaXplKSwgeT12YWx1ZSwgZ3JvdXA9ZmFjdG9yKHNvdXJjZSkpLAogICAgICAgICAgICAgcG9zaXRpb249cG9zaXRpb25fZG9kZ2UoMC43NSksIHNoYXBlPTE4LCBzaXplPTQsIGNvbG9yPSJ3aGl0ZSIpICsKICAjIFRoZW4gYWRkIHRoZSBhY3R1YWwgcG9pbnQgaW4gYmxhY2sKICBnZW9tX3BvaW50KGRhdGE9YWdnci53ci5tLCBhZXMoeD1mYWN0b3IoYmxvY2tzaXplKSwgeT12YWx1ZSwgZ3JvdXA9ZmFjdG9yKHNvdXJjZSkpLAogICAgICAgICAgICAgcG9zaXRpb249cG9zaXRpb25fZG9kZ2UoMC43NSksIHNoYXBlPTE4LCBzaXplPTMsIGNvbG9yPSJibGFjayIpICsKICBzY2FsZV9zaGFwZV9tYW51YWwobmFtZT0iU3VicmVnaW9uIiwgdmFsdWVzPWMoIldoaXRlIFJpdmVyIE5GIiA9IDgpKSArCiAgZ3VpZGVzKAogICAgZmlsbD1ndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzPWxpc3Qoc2hhcGU9TkEpKSwgIyBObyBzaGFwZSBpbiB0aGUgZmlsbCBsZWdlbmQKICAgIHNoYXBlPWd1aWRlX2xlZ2VuZCh0aXRsZT0iICIsIG92ZXJyaWRlLmFlcz1saXN0KGNvbG9yPSJibGFjayIpKSAjIFNoYXBlIGxlZ2VuZCBmb3IgU3VicmVnaW9uCiAgKQpmNwoKIyBTYXZlIG91dApnZ3NhdmUoZjcsIGZpbGUgPSAiLi4vLi4vZmlndXJlcy9GaWd1cmU3X0FncmVlbWVudF9Cb3gucG5nIiwKICAgICAgIGRwaSA9IDMwMCwgYmc9IndoaXRlIikgIyBhZGp1c3QgZHBpIGFjY29yZGluZ2x5CmBgYApgYGB7cn0KIyBBZGQgYSBzcGF0aWFsIG1hcCBvZiBzdGF0aXN0aWNzIGJ5IGJsb2NrcwpibG9ja3NfIDwtIGJsb2NrcyAlPiUKICBtdXRhdGUoaWQgPSBhcy5udW1lcmljKGlkKS0xKSAlPiUKICByZW5hbWUoYmxvY2sgPSBpZCkgJT4lCiAgbGVmdF9qb2luKGFnZ3Iuc3IsIGJ5PSJibG9jayIpICU+JQogIHBpdm90X2xvbmdlcihjb2xzID0gYyhwcmVjaXNpb24sIHJlY2FsbCwgZjEpLCBuYW1lc190byA9ICJzdGF0aXN0aWMiLCB2YWx1ZXNfdG8gPSAidmFsdWUiKSAlPiUKICBuYS5vbWl0KCkKCmY3YiA8LSBnZ3Bsb3QoZGF0YSA9IGJsb2Nrc18pICsKICBnZW9tX3NmKGFlcyhmaWxsID0gdmFsdWUpKSArCiAgZ2VvbV9zZihkYXRhPXNybWUsIGZpbGw9TkEsIGNvbG9yPSJncmV5MjAiLCBsaW5ld2lkdGg9MC40KSArCiAgZmFjZXRfZ3JpZChzb3VyY2UgfiBzdGF0aXN0aWMpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfYyhvcHRpb24gPSAiQyIpICsgIyBVc2UgYSBjb250aW51b3VzIGNvbG9yIHNjYWxlCiAgdGhlbWVfdm9pZCgpICsKICB0aGVtZSgKICAgIHN0cmlwLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIKICApICsKICBndWlkZXMoZmlsbCA9IGd1aWRlX2NvbG91cmJhcihkaXJlY3Rpb24gPSAiaG9yaXpvbnRhbCIsIGJhcndpZHRoID0gMTAsIGJhcmhlaWdodCA9IDAuNjAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGlja3M9RiwgdGl0bGUucG9zaXRpb24gPSAibGVmdCIpLAogICAgICAgICBsYWJlbC50aGVtZSA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDAsIHNpemUgPSA5KSkgKwogIGxhYnMoZmlsbD0iIiwgdGFnPSJCIikKZjdiCgojIFNhdmUgb3V0Cmdnc2F2ZShmN2IsIGZpbGUgPSAiLi4vLi4vZmlndXJlcy9GaWd1cmU3X0FncmVlbWVudF9NYXBzLnBuZyIsCiAgICAgICBkcGkgPSAzMDAsIGJnPSJ3aGl0ZSIpICMgYWRqdXN0IGRwaSBhY2NvcmRpbmdseQpgYGAKCkpvaW4gdGhlIHR3byBwbG90cy4KCmBgYHtyfQoKZjcgPC0gZjcgKyB0aGVtZShwbG90Lm1hcmdpbiA9IHVuaXQoYygwLjUsIDEwLCAwLjUsIDAuNSksIm1tIikpICMgQWRkIHJpZ2h0IG1hcmdpbiB0byB0aGUgbGVmdCBwbG90CmY3YiA8LSBmN2IgKyB0aGVtZShwbG90Lm1hcmdpbiA9IHVuaXQoYygwLjUsIDAuNSwgMTAsIDAuNSksIm1tIikpICMgQWRkIGxlZnQgbWFyZ2luIHRvIHRoZSByaWdodCBwbG90CgphcnI3IDwtIGdnYXJyYW5nZShmNywgZjdiLCBuY29sPTIsIGFsaWduPSJoIiwgd2lkdGhzID0gYygxLjUsMSkpCmFycjcKCiMgU2F2ZSBvdXQKZ2dzYXZlKGFycjcsIGZpbGUgPSAiLi4vLi4vZmlndXJlcy9GaWd1cmU3X0FncmVlbWVudF9Cb3hfTWFwc19BcnIucG5nIiwKICAgICAgIGRwaSA9IDMwMCwgYmc9IndoaXRlIikgIyBhZGp1c3QgZHBpIGFjY29yZGluZ2x5CmBgYAoKCmBgYHtyfQoKYnJld2VyLnBhbChuPTUsIlNldDEiKQoKY29scyA8LSBjKCIjNERBRjRBIiwgIiM5ODRFQTMiLCAiI0ZGN0YwMCIpCgpnbGltcHNlKHJlZi5nbG9iYWwpCgojICMgR2V0IGFuIGF2ZXJhZ2UgZjEgc2NvcmUgdGFibGUKIyBmMW1uIDwtIHJlZi5nbG9iYWwgJT4lCiMgICBncm91cF9ieQoKIyBBZGQgYSBzdGF0aXN0aWNzIGNvbHVtbiBmb3IgbGVnZW5kIChjb3VsZCBkbyB0aGlzIGZvciBGMSB0b28gLi4uKQpyZWYuZ2xvYmFsLm0gPC0gcmVzaGFwZTI6Om1lbHQoCiAgcmVmLmdsb2JhbCwgaWQudmFycyA9IGMoImJsb2Nrc2l6ZSIsICJzb3VyY2UiLCAicmVnaW9uIiksIAogIG1lYXN1cmUudmFycyA9IGMoInByZWMiLCAicmVjIiksIHZhcmlhYmxlLm5hbWUgPSAic3RhdGlzdGljIgopCgpnbGltcHNlKHJlZi5nbG9iYWwubSkKCmY3YSA8LSBnZ3Bsb3QoZGF0YT1yZWYuZ2xvYmFsLm0pICsKICBnZW9tX2xpbmUoYWVzKHg9YmxvY2tzaXplLHk9dmFsdWUsY29sb3I9ZmFjdG9yKHNvdXJjZSksIGxpbmV0eXBlPXN0YXRpc3RpYyksIGxpbmV3aWR0aD0wLjYpICsKICBnZW9tX3BvaW50KGRhdGE9cmVmLmdsb2JhbCwgYWVzKHg9YmxvY2tzaXplLCB5PWYxLCBjb2xvcj1zb3VyY2UpKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcz1jKDEsMyw1LDcsOSkpICsKICBzY2FsZV9saW5ldHlwZV9tYW51YWwodmFsdWVzID0gYygicHJlYyIgPSAiZG90dGVkIiwgInJlYyIgPSAic29saWQiKSwgbmFtZT0iU3RhdGlzdGljOiAiLAogICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YygiUHJlY2lzaW9uIiwiUmVjYWxsIikpICsKICBmYWNldF93cmFwKH5yZWdpb24sIHNjYWxlcyA9ICJmaXhlZCIpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWNvbHMsIGxhYmVscz1jKCJMQU5ERklSRSBFVlQiLCJVU0ZTIFRyZWVNYXAiLCJVU0ZTIElUU1AiKSwgbmFtZT0iU291cmNlOiAiKSArCiAgdGhlbWVfbGlnaHQoMTQpICsKICB5bGltKDAsMSkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiLAogICAgICAgIGxlZ2VuZC5ib3ggPSAidmVydGljYWwiLAogICAgICAgIGxlZ2VuZC5qdXN0aWZpY2F0aW9uID0gYygwLjUsMC41KSwgIyBMZWZ0LWFsaWducyB0aGUgbGVnZW5kcwogICAgICAgIGxlZ2VuZC5zcGFjaW5nLnkgPSB1bml0KC0xMCwgInB0IiksICMgQWRqdXN0cyB0aGUgZ2FwIGJldHdlZW4gdGhlIGxlZ2VuZHMKICAgICAgICBsZWdlbmQudGV4dCA9IGdndGV4dDo6ZWxlbWVudF9tYXJrZG93bihoYWxpZ24gPSAwKSwgIyBUaGlzIGFsaWducyBlYWNoIGluZGl2aWR1YWwgbGVnZW5kJ3MgdGV4dCB0byB0aGUgbGVmdAogICAgICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT0xMixmYWNlPSJpdGFsaWMiKSwKICAgICAgICB0ZXh0ID0gZWxlbWVudF90ZXh0KGZhbWlseSA9ICJBcmlhbCIpKSArCiAgbGFicyh4PSJCbG9ja3NpemUgKHBpeGVscykiLHk9IlZhbHVlIikKICAKZjdhCgpnZ3NhdmUoZjdhLCBmaWxlID0gIi4uLy4uL2ZpZ3VyZXMvRmlndXJlN19HbG9iYWxfUHJlY1JlY19BZ3JlZW1lbnQucG5nIiwgZHBpPTMwMCkKCmBgYAoKQ2xlYW4gdXAhIAoKYGBge3J9CnJtKHJlZi5nbG9iYWwscmVmLmdsb2JhbC5tLGY3YSxmN2IsZjcscmVmLmZvY2FsKQpnYygpCmBgYAoKIyMgRmlndXJlIDg6IExhbmRzY2FwZSBQYXRjaCBEeW5hbWljcwoKYGBge3J9CmNsYXNzLnNyXyA8LSBjbGFzcy5zciAlPiUKICBtdXRhdGUoc291cmNlID0gaWZfZWxzZShzb3VyY2U9PSJsYzE2X2V2dF8yMDBfYmluX3NybWVfMTBtIiwgIkxBTkRGSVJFIEVWVCIsIHNvdXJjZSksCiAgICAgICAgIHNvdXJjZSA9IGlmX2Vsc2Uoc291cmNlPT0iczJhc3Blbl9wcm9iXzEwbV9iaW5PcHRfc3JtZSIsICJTZW50aW5lbC1iYXNlZCBNYXAiLCBzb3VyY2UpLAogICAgICAgICBzb3VyY2UgPSBpZl9lbHNlKHNvdXJjZT09InVzZnNfaXRzcF9hc3Blbl9iYV9ndDEwX3NybWVfMTBtIiwgIlVTRlMgSVRTUCIsIHNvdXJjZSksCiAgICAgICAgIHNvdXJjZSA9IGlmX2Vsc2Uoc291cmNlPT0idXNmc190cmVlbWFwMTZfYmFsaXZlX2ludF9iaW5fc3JtZV8xMG0iLCAiVVNGUyBUcmVlTWFwIiwgc291cmNlKSkKcGF0Y2guc3JfIDwtIHBhdGNoLnNyICU+JQogIG11dGF0ZShzb3VyY2UgPSBpZl9lbHNlKHNvdXJjZT09ImxjMTZfZXZ0XzIwMF9iaW5fc3JtZV8xMG0iLCAiTEFOREZJUkUgRVZUIiwgc291cmNlKSwKICAgICAgICAgc291cmNlID0gaWZfZWxzZShzb3VyY2U9PSJzMmFzcGVuX3Byb2JfMTBtX2Jpbk9wdF9zcm1lIiwgIlNlbnRpbmVsLWJhc2VkIE1hcCIsIHNvdXJjZSksCiAgICAgICAgIHNvdXJjZSA9IGlmX2Vsc2Uoc291cmNlPT0idXNmc19pdHNwX2FzcGVuX2JhX2d0MTBfc3JtZV8xMG0iLCAiVVNGUyBJVFNQIiwgc291cmNlKSwKICAgICAgICAgc291cmNlID0gaWZfZWxzZShzb3VyY2U9PSJ1c2ZzX3RyZWVtYXAxNl9iYWxpdmVfaW50X2Jpbl9zcm1lXzEwbSIsICJVU0ZTIFRyZWVNYXAiLCBzb3VyY2UpLAogICAgICAgICBhcmVhX3F0ID0gZmFjdG9yKG50aWxlKGFyZWEsIDQpLCBsYWJlbHMgPSBjKCJRMSIsICJRMiIsICJRMyIsICJRNCIpKSkKZ2xpbXBzZShwYXRjaC5zcl8pCmdsaW1wc2UoY2xhc3Muc3JfKQoKY2xhc3Mud3JfIDwtIGNsYXNzLndyICU+JQogIG11dGF0ZShyZWdpb24gPSAid3JuZiIpICU+JQogIG11dGF0ZShzb3VyY2UgPSBpZl9lbHNlKHNvdXJjZT09ImxjMTZfZXZ0XzIwMF9iaW5fd3JuZl8xMG0iLCAiTEFOREZJUkUgRVZUIiwgc291cmNlKSwKICAgICAgICAgc291cmNlID0gaWZfZWxzZShzb3VyY2U9PSJzMmFzcGVuX3Byb2JfMTBtX2Jpbk9wdF93cm5mIiwgIlNlbnRpbmVsLWJhc2VkIE1hcCIsIHNvdXJjZSksCiAgICAgICAgIHNvdXJjZSA9IGlmX2Vsc2Uoc291cmNlPT0idXNmc19pdHNwX2FzcGVuX2JhX2d0MTBfd3JuZl8xMG0iLCAiVVNGUyBJVFNQIiwgc291cmNlKSwKICAgICAgICAgc291cmNlID0gaWZfZWxzZShzb3VyY2U9PSJ1c2ZzX3RyZWVtYXAxNl9iYWxpdmVfaW50X2Jpbl93cm5mXzEwbSIsICJVU0ZTIFRyZWVNYXAiLCBzb3VyY2UpKQpwYXRjaC53cl8gPC0gcGF0Y2gud3IgJT4lCiAgbXV0YXRlKHJlZ2lvbiA9ICJ3cm5mIikgJT4lCiAgbXV0YXRlKHNvdXJjZSA9IGlmX2Vsc2Uoc291cmNlPT0ibGMxNl9ldnRfMjAwX2Jpbl93cm5mXzEwbSIsICJMQU5ERklSRSBFVlQiLCBzb3VyY2UpLAogICAgICAgICBzb3VyY2UgPSBpZl9lbHNlKHNvdXJjZT09InMyYXNwZW5fcHJvYl8xMG1fYmluT3B0X3dybmYiLCAiU2VudGluZWwtYmFzZWQgTWFwIiwgc291cmNlKSwKICAgICAgICAgc291cmNlID0gaWZfZWxzZShzb3VyY2U9PSJ1c2ZzX2l0c3BfYXNwZW5fYmFfZ3QxMF93cm5mXzEwbSIsICJVU0ZTIElUU1AiLCBzb3VyY2UpLAogICAgICAgICBzb3VyY2UgPSBpZl9lbHNlKHNvdXJjZT09InVzZnNfdHJlZW1hcDE2X2JhbGl2ZV9pbnRfYmluX3dybmZfMTBtIiwgIlVTRlMgVHJlZU1hcCIsIHNvdXJjZSksCiAgICAgICAgIGFyZWFfcXQgPSBmYWN0b3IobnRpbGUoYXJlYSwgNCksIGxhYmVscyA9IGMoIlExIiwgIlEyIiwgIlEzIiwgIlE0IikpKQpybShwYXRjaC5zcixjbGFzcy5zcixwYXRjaC53cixjbGFzcy53cikKYGBgCgpHZXQgc29tZSBzdW1tYXJ5IHN0YXRpc3RpY3M6CgpgYGB7cn0KY2xhc3Muc3JfICU+JQogIGdyb3VwX2J5KHNvdXJjZSkgJT4lCiAgc3VtbWFyaXplKAogICAgdG90YWxfYXJlYV9rbTIgPSBzdW0odG90YWxfYXJlYSkgKiAwLjAxCiAgKSAlPiUKICBnZ3Bsb3QoYWVzKHg9cmVvcmRlcihzb3VyY2UsIHRvdGFsX2FyZWFfa20yKSwgeT10b3RhbF9hcmVhX2ttMikpICsKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIsIGZpbGw9ImxpZ2h0IGdyZWVuIikgKwogIHRoZW1lX2xpZ2h0KDEyKQpgYGAKCiMjIFRhYmxlIFg6IENsYXNzLWxldmVsIHN0YXRpc3RpY3MKCmBgYHtyfQpkZjEgPC0gY2xhc3Muc3JfICU+JQogIGdyb3VwX2J5KHNvdXJjZSkgJT4lCiAgc3VtbWFyaXplKAogICAgdG90YWxfYXJlYV9rbTIgPSBzdW0odG90YWxfYXJlYSkgKiAwLjAxLAogICAgbl9wYXRjaF90b3RhbCA9IHN1bShuX3BhdGNoKSwKICAgIHBhdGNoX2RlbnNpdHlfbW4gPSBtZWFuKHBhdGNoX2RlbikKICApCgpkZjIgPC0gcGF0Y2guc3JfICU+JQogIGdyb3VwX2J5KHNvdXJjZSkgJT4lCiAgc3VtbWFyaXplKAogICAgcGF0Y2hfc2l6ZV9tbiA9IG1lYW4oYXJlYSksCiAgICBwZXJfYXJfcl9tbiA9IG1lYW4ocGVyaW1ldGVyX2FyZWFfcmF0aW8pCiAgKQoKZGYgPC0gbGVmdF9qb2luKGRmMSxkZjIsYnk9InNvdXJjZSIpCmhlYWQoZGYpCgpybShkZjEsZGYyKQoKIyBNYWtlIGEgdGlkeSB0YWJsZQojIFNldCBmb250IG5hbWUgZm9yIHRhYmxlCmZvbnRuYW1lIDwtICJUaW1lcyBOZXcgUm9tYW4iCiMgQ2xlYW4gdXAgZXh0cmEgcm93cyBhbmQgZGlnaXRzCmNsZWFuZWQgPC0gZGYgJT4lCiBtdXRhdGVfaWYoaXMuZG91YmxlLCB+IHJvdW5kKC4sIGRpZ2l0cyA9IDIpKSAlPiUKICMgU29ydCBmcm9tIGZhc3Rlc3QgdG8gc2xvd2VzdAogYXJyYW5nZSh0b3RhbF9hcmVhX2ttMikKIyBGaXggbmFtZXMgYW5kIGFkZCB1bml0cwpuYW1lcyhjbGVhbmVkKSA8LSBjKCJEYXRhIFNvdXJjZSIsIlRvdGFsIEFyZWEgKEttMikiLCJOdW1iZXIgb2YgUGF0Y2hlcyIsIlBhdGNoIERlbnNpdHkiLAogICAgICAgICAgICAgICAgICAgICJBdmVyYWdlIFBhdGNoIFNpemUgKGhhKSIsIkF2ZXJhZ2UgUGVyaW1ldGVyL0FyZWEgUmF0aW8iKQojIENyZWF0ZSB0aGUgZmxleHRhYmxlCmZ0IDwtIGZsZXh0YWJsZTo6ZmxleHRhYmxlKGNsZWFuZWQpICU+JQogIGZsZXh0YWJsZTo6Zm9udChmb250bmFtZSA9IGZvbnRuYW1lLCBwYXJ0ID0gImFsbCIpICU+JQogIGZsZXh0YWJsZTo6YXV0b2ZpdCgpICU+JSAKICBmbGV4dGFibGU6OmZpdF90b193aWR0aCg3LjUpCmZ0CnByaW50KGZ0LCBwcmV2aWV3ID0gImRvY3giKQoKYGBgCgpQbG90IHRoZSB0b3RhbCBhcmVhIGJveCBwbG90IGFjcm9zcyBzcGF0aWFsIGJsb2NrcyBmb3IgdGhlIFNvdXRoZXJuIFJvY2tpZXM6CgpgYGB7ciBmaWcud2lkdGg9Ni41LCBmaWcuaGVpZ2h0PTMuNX0KCiMgQ29tYmluZSB0aGUgV2hpdGUgUml2ZXIgTkYgYW5kIFNSTUUgdG8gcGxvdCBwYXRjaCBzdGF0aXN0aWNzCmRmIDwtIGJpbmRfcm93cyhwYXRjaC5zcl8scGF0Y2gud3JfKQojIFBsb3QgZmFjZXQgd3JhcCBvZiBwYXRjaCBzaXplCmRmIDwtIGRmICU+JQogIGdyb3VwX2J5KHNvdXJjZSxyZWdpb24pICU+JQogIHN1bW1hcml6ZSgKICAgIHBhdGNoX3NpemUgPSBtZWFuKGFyZWEpLAogICAgcGVyX2FyX3IgPSBtZWFuKHBlcmltZXRlcl9hcmVhX3JhdGlvKQogICkgCmdsaW1wc2UoZGYpCgojIEdldCB0aGUgY2xhc3MgbWV0cmljcyBpbiB0aGUgc2FtZSBmb3JtYXQKZGYyIDwtIGJpbmRfcm93cyhjbGFzcy5zcl8sY2xhc3Mud3JfKSAlPiUKICBncm91cF9ieShzb3VyY2UscmVnaW9uKSAlPiUKICBzdW1tYXJpemUoCiAgICBwYXRjaF9kZW4gPSBtZWFuKHBhdGNoX2RlbikKICApCgpkZl8gPC0gbGVmdF9qb2luKGRmLGRmMixieT1jKCJyZWdpb24iLCJzb3VyY2UiKSkKCiMgUGl2b3QgbG9uZ2VyCgptZXRyaWNfbGFiZWxzIDwtIGMocGF0Y2hfc2l6ZSA9ICJQYXRjaCBTaXplIChoYSkiLAogICAgICAgICAgICAgICAgICAgcGVyX2FyX3IgPSAiUGVyaW1ldGVyLUFyZWEgUmF0aW8iLAogICAgICAgICAgICAgICAgICAgcGF0Y2hfZGVuID0gIlBhdGNoIERlbnNpdHkiKQoKZGYubG9uZyA8LSBkZl8gJT4lCiAgcGl2b3RfbG9uZ2VyKGNvbHMgPSBjKGNvbnRhaW5zKCJfIikpLAogICAgICAgICAgICAgICBuYW1lc190byA9ICJtZXRyaWMiLAogICAgICAgICAgICAgICB2YWx1ZXNfdG8gPSAidmFsdWUiKSAlPiUKICBtdXRhdGUocmVnaW9uID0gaWZfZWxzZShyZWdpb249PSJzcm1lIiwiU291dGhlcm4gUm9ja2llcyIsIldoaXRlIFJpdmVyIE5GIiksCiAgICAgICAgIHNvdXJjZSA9IGlmX2Vsc2Uoc291cmNlPT0iU2VudGluZWwtYmFzZWQgTWFwIiwiU2VudGluZWwtYmFzZWQiLHNvdXJjZSksCiAgICAgICAgIHNvdXJjZSA9IGZhY3Rvcihzb3VyY2UsIGxldmVscyA9IHJldihsZXZlbHMocmVvcmRlcihzb3VyY2UsIHZhbHVlKSkpKSkKCmRmLmxvbmckc291cmNlIDwtIGZhY3RvcihkZi5sb25nJHNvdXJjZSxsZXZlbHM9YygnU2VudGluZWwtYmFzZWQnLCAnTEFOREZJUkUgRVZUJywgJ1VTRlMgVHJlZU1hcCcsICdVU0ZTIElUU1AnKSkKCmNvbHMgPC0gYygiIzAwNWEzMiIsIiNhMWQ5OWIiKQoKIyBHcm91cGVkIGJhciBjaGFydApmNyA8LSBnZ3Bsb3QoZGF0YT1kZi5sb25nLCBhZXMoeD1zb3VyY2UsIHk9dmFsdWUsIGZpbGw9ZmFjdG9yKHJlZ2lvbikpKSArCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiLCBwb3NpdGlvbj1wb3NpdGlvbl9kb2RnZSgpKSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWNvbHMpICsKICBmYWNldF93cmFwKH5tZXRyaWMsIHNjYWxlcyA9ICJmcmVlX3kiLCBsYWJlbGxlciA9IGxhYmVsbGVyKG1ldHJpYyA9IG1ldHJpY19sYWJlbHMpKSArICMgRmFjZXRpbmcgYnkgc3VtbWFyeSB2YXJpYWJsZQogIGxhYnMoeD0iIiwgeT0iVmFsdWUiLCBmaWxsPSJSZWdpb24iKSArICMgQWRkaW5nIGxhYmVscwogIHRoZW1lX2J3KDEwKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInRvcCIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZT00NSwgaGp1c3Q9MSkpICMgSW1wcm92aW5nIHJlYWRhYmlsaXR5IG9mIHgtYXhpcyBsYWJlbHMKZjcKCmdnc2F2ZShmNywgZmlsZSA9ICIuLi8uLi9maWd1cmVzL0ZpZ3VyZTdfTGFuZHNjYXBlX1N1bW1hcmllcy5wbmciLCBkcGk9MzAwKQoKCmBgYAoKQ2FsY3VsYXRlIHNvbWUgZ3JvdXBlZCBzdGF0aXN0aWNzICh0byBjb21wYXJlIHdpdGggdGhlIGNsYXNzIG1ldHJpY3MgcmVzdWx0cykKCmBgYHtyIG1lc3NhZ2U9Riwgd2FybmluZz1GLCBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD04fQoKIyBBbHNvIGNyZWF0ZSBhIGdyb3VwZWQgc3VtbWFyeQoocGF0Y2guc3VtbWFyeSA8LSBwYXRjaF9tZXRyaWNzICU+JQogIGdyb3VwX2J5KHNvdXJjZSxyZWdpb24pICU+JQogIHN1bW1hcml6ZShhcmVhX21kID0gbWVkaWFuKGFyZWEpLCAjIGNvbnZlcnQgdG8gaGVjdGFyZXMKICAgICAgICAgICAgYXJlYV9tbiA9IG1lYW4oYXJlYSksCiAgICAgICAgICAgIGFyZWFfc3VtID0gc3VtKGFyZWEpLAogICAgICAgICAgICBwZXJpbV9tZCA9IG1lZGlhbihwZXJpbWV0ZXIpLAogICAgICAgICAgICBwZXJpbV9tbiA9IG1lYW4ocGVyaW1ldGVyKSwKICAgICAgICAgICAgcGFyX21kID0gbWVkaWFuKHBlcmltZXRlcl9hcmVhX3JhdGlvKSwKICAgICAgICAgICAgcGFyX21uID0gbWVhbihwZXJpbWV0ZXJfYXJlYV9yYXRpbyksCiAgICAgICAgICAgIHNpX21kID0gbWVkaWFuKHNoYXBlX2luZGV4KSwKICAgICAgICAgICAgc2lfbW4gPSBtZWFuKHNoYXBlX2luZGV4KSkgJT4lCiAgdW5ncm91cCgpKQoKKHBhdGNoLmxvbmcgPC0gcGF0Y2guc3VtbWFyeSAlPiUKICBwaXZvdF9sb25nZXIoY29scyA9IGMoY29udGFpbnMoIl8iKSksCiAgICAgICAgICAgICAgIG5hbWVzX3RvID0gIm1ldHJpYyIsCiAgICAgICAgICAgICAgIHZhbHVlc190byA9ICJ2YWx1ZSIpKQoKcGF0Y2gubG9uZyRzb3VyY2UgPC0gZmFjdG9yKHBhdGNoLmxvbmckc291cmNlLGxldmVscz1jKCdBc3BlbjEwbScsICdBc3BlbjMwbScsICdMRkVWVCcsICdUcmVlTWFwJywgJ0lUU1AnKSkKCihnZ3Bsb3QoZGF0YSA9IHBhdGNoLmxvbmcsIGFlcyh4PXNvdXJjZSwgeT12YWx1ZSwgZmlsbD1yZWdpb24pKSArCiAgICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIsIHBvc2l0aW9uPSJkb2RnZSIsIHdpZHRoPTAuNykgKwogICAgZmFjZXRfd3JhcCh+bWV0cmljLCBzY2FsZXM9ImZyZWUiKSArIAogICAgdGhlbWVfbWluaW1hbCgxNCkgKwogICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSwKICAgIHRleHQgPSBlbGVtZW50X3RleHQoZmFtaWx5ID0gIkFyaWFsIikpKQoKKGY4cyA8LSBnZ3Bsb3QoZGF0YSA9IHBhdGNoLmxvbmcsIGFlcyh4PXNvdXJjZSwgeT12YWx1ZSwgZmlsbD1yZWdpb24pKSArCiAgICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIsIHBvc2l0aW9uPSJkb2RnZSIsIHdpZHRoPTAuNykgKyAKICAgIHRoZW1lX21pbmltYWwoMTQpICsKICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSksCiAgICB0ZXh0ID0gZWxlbWVudF90ZXh0KGZhbWlseSA9ICJBcmlhbCIpKSkKCmBgYAoKR3JhYiBzb21lIHN1bW1hcnkgc3RhdGlzdGljcyBvbiB0aGUgcGF0Y2ggZHluYW1pY3M6CgpgYGB7cn0KCmEgPC0gcGF0Y2gubG9uZyAlPiUgZmlsdGVyKHJlZ2lvbj09IlNSTUUiLG1ldHJpYz09ImFyZWFfbW4iLHNvdXJjZT09IkFzcGVuMTBtIikKYiA8LSBwYXRjaC5sb25nICU+JSBmaWx0ZXIocmVnaW9uPT0iU1JNRSIsbWV0cmljPT0iYXJlYV9tbiIsc291cmNlPT0iTEZFVlQiKQoKcHJpbnQoIkRpZmZlcmVuY2UgaW4gbWVhbiBwYXRjaCBzaXplIGZvciB0aGUgU1JNRTogIikKKChiJHZhbHVlLWEkdmFsdWUpL2IkdmFsdWUpKjEwMAoKYSA8LSBwYXRjaC5sb25nICU+JSBmaWx0ZXIocmVnaW9uPT0iV1JORiIsbWV0cmljPT0iYXJlYV9tbiIsc291cmNlPT0iQXNwZW4xMG0iKQpiIDwtIHBhdGNoLmxvbmcgJT4lIGZpbHRlcihyZWdpb249PSJXUk5GIixtZXRyaWM9PSJhcmVhX21uIixzb3VyY2U9PSJMRkVWVCIpCgpwcmludCgiRGlmZmVyZW5jZSBpbiBtZWFuIHBhdGNoIHNpemUgZm9yIHRoZSBXUk5GOiAiKQooKGIkdmFsdWUtYSR2YWx1ZSkvYiR2YWx1ZSkqMTAwCgpwcmludCgifn5+fn5+fn5+fn5+fn5+fiIpCgpwcmludCgiRGlmZmVyZW5jZSBpbiBtZWFuIHBhdGNoIHNpemUgZm9yIHRoZSBTUk1FOiAiKQooKGIkdmFsdWUtYSR2YWx1ZSkvYiR2YWx1ZSkqMTAwCgooYyA8LSBwYXRjaC5sb25nICU+JSBmaWx0ZXIobWV0cmljPT0iYXJlYV9tZCIsc291cmNlPT0iQXNwZW4xMG0iKSkKKGQgPC0gcGF0Y2gubG9uZyAlPiUgZmlsdGVyKG1ldHJpYz09ImFyZWFfbWQiLHNvdXJjZT09IkxGRVZUIikpCgpwcmludCgiTWVhbiBhbmQgbWVkaWFuIHBhdGNoIHNpemVzOiAiKQpwYXN0ZTAoIkFzcGVuMTBtIG1lYW46ICIsYSR2YWx1ZSkKcGFzdGUwKCJMRkVWVCBtZWFuOiAiLGIkdmFsdWUpCnBhc3RlMCgiQXNwZW4xMG0gbWVkaWFuOiAiLGMkdmFsdWUpCnBhc3RlMCgiTEZFVlQgbWVhbjogIixkJHZhbHVlKQoKCgpwcmludCgiRGlmZmVyZW5jZSBpbiBtZWRpYW4gYXJlYTogIikKKChkJHZhbHVlLWMkdmFsdWUpL2QkdmFsdWUpKjEwMAoKcHJpbnQoIk1lYW4gUGVyaW1ldGVyIGFyZWEgcmF0aW86ICIpCihhIDwtIHBhdGNoLmxvbmcgJT4lIGZpbHRlcihtZXRyaWM9PSJwYXJfbW4iLHNvdXJjZT09IkFzcGVuMTBtIikpCihiIDwtIHBhdGNoLmxvbmcgJT4lIGZpbHRlcihtZXRyaWM9PSJwYXJfbW4iLHNvdXJjZT09IkxGRVZUIikpCgpwcmludCgiRGlmZmVyZW5jZSBpbiBQQVI6ICIpCigoYiR2YWx1ZS1hJHZhbHVlKS9iJHZhbHVlKSoxMDAKCnJtKGEsYixjLGQpCmBgYAoKQm94cGxvdCBhcyBmYWNldCB3cmFwIGZvciBwYXRjaCBtZXRyaWNzOgoKYGBge3IgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9MTB9CgpicmV3ZXIucGFsKG49NSwiU2V0MSIpCgpjb2xzIDwtIGMoIiMwMDVhMzIiLCIjYTFkOTliIikKCihkZiA8LSBwYXRjaF9tZXRyaWNzICU+JQogICMgbXV0YXRlKGFyZWFfaGEgPSBhcmVhKjEwMCkgJT4lCiAgc2VsZWN0KC1jKFgsaW5kZXgsc2hhcGVfaW5kZXgpKSAlPiUKICBwaXZvdF9sb25nZXIoY29udGFpbnMoYygiYXJlYSIsInBlcmltZXRlcl9hcmVhX3JhdGlvIikpKSAlPiUKICBhcnJhbmdlKHNvdXJjZSwgZGVzYyh2YWx1ZSkpICU+JQogIG11dGF0ZShuYW1lID0gYXMuY2hhcmFjdGVyKG5hbWUpLAogICAgICAgICBuYW1lID0gcmVjb2RlKG5hbWUsCiAgICAgICAgICAgICAgICAgICAgICAgImFyZWEiID0gIlBhdGNoIFNpemUgKGhhKSIsCiAgICAgICAgICAgICAgICAgICAgICAgInBlcmltZXRlcl9hcmVhX3JhdGlvIiA9ICJQZXJpbWV0ZXIvQXJlYSBSYXRpbyIpKSkKCmRmJHNvdXJjZSA8LSBmYWN0b3IoZGYkc291cmNlLAogICAgICAgICAgICAgICAgICAgIGxldmVscz1jKCdBc3BlbjEwbScsICdBc3BlbjMwbScsICdMRkVWVCcsICdUcmVlTWFwJywgJ0lUU1AnKSkKIAooZjhhIDwtIGdncGxvdChkYXRhPWRmLCBhZXMoeSA9IHZhbHVlLCB4ID0gZmFjdG9yKHNvdXJjZSksIGZpbGwgPSBmYWN0b3IocmVnaW9uKSkpICsKICBnZW9tX2JveHBsb3Qob3V0bGllci5zaXplID0gMC4yKSArCiAgc2NhbGVfeV9jb250aW51b3VzKHRyYW5zPSJsb2cxMCIsbGFiZWxzPWxhYmVsX251bWJlcl9zaShhY2N1cmFjeSA9IDEpKSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWNvbHMpICsKICBmYWNldF93cmFwKH5uYW1lLCBzY2FsZXMgPSAiZnJlZV95IiwgbnJvdz0xKSArCiAgbGFicyh4PSJcbkRhdGEgU291cmNlIix5PSJWYWx1ZSIsdGl0bGU9IlBhdGNoIE1ldHJpY3MiLCBmaWxsPSJSZWdpb246ICIpICsKICB0aGVtZV9saWdodCgxMikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSwgc2l6ZT0xMSksCiAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoYW5nbGUgPSAwLCB2anVzdD0wLjEsIHNpemU9MTEpLAogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChzaXplPTEyLCBmYWNlPSJpdGFsaWMiLCB2anVzdD0xKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoc2l6ZT0xMiwgZmFjZT0iaXRhbGljIiwgdmp1c3Q9LTEpLAogICAgICAgIHBsb3QubWFyZ2luID0gdW5pdChjKDAuNSwwLjUsMC41LDAuNSksImNtIiksCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uPSJ0b3AiLAogICAgICAgIGxlZ2VuZC5zcGFjaW5nLnggPSB1bml0KDAuNSwiY20iKSwKICAgICAgICB0ZXh0ID0gZWxlbWVudF90ZXh0KGZhbWlseSA9ICJBcmlhbCIpKSkKCmdnc2F2ZShmOGEsIGZpbGUgPSAiLi4vLi4vZmlndXJlcy9GaWd1cmU4QV9wYXRjaF9tZXRyaWNzLnBuZyIsIGRwaSA9IDMwMCkgIyBhZGp1c3QgZHBpIGFjY29yZGluZ2x5CgpgYGAKClBsb3QgY2xhc3MgbWV0cmljcyBhcyBmYWNldCB3cmFwOgoKYGBge3IgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9MTAsIG1lc3NhZ2U9Rn0KCiMgQ2FsY3VsYXRlIGFyZWEgaW4gc3F1YXJlIG1ldGVycwpzcm1lX2FyZWFfa20yIDwtIHN0X2FyZWEoc3JtZSkgLyAxZTYKd3JuZl9hcmVhX2ttMiA8LSBzdF9hcmVhKHdybmYpIC8gMWU2CgooZGYgPC0gY2xhc3NfbWV0cmljcyAlPiUKICBtdXRhdGUodG90YWxfYXJlYSA9IHRvdGFsX2FyZWEqMC4wMSwKICAgICAgICAgcHJvcF9hcmVhID0gaWZfZWxzZShyZWdpb249PSJTUk1FIiwgYXMuZG91YmxlKCh0b3RhbF9hcmVhL3NybWVfYXJlYV9rbTIqMTAwKSksIDAuMCksCiAgICAgICAgIHByb3BfYXJlYSA9IGlmX2Vsc2UocmVnaW9uPT0iV1JORiIsIGFzLmRvdWJsZSgodG90YWxfYXJlYS93cm5mX2FyZWFfa20yKjEwMCkpLCBwcm9wX2FyZWEpKSAlPiUKICBzZWxlY3QoLWMoWCkpICU+JQogIHBpdm90X2xvbmdlcihjb2xzID0gYyhjb250YWlucygiXyIpKSwKICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAibWV0cmljIiwKICAgICAgICAgICAgICAgdmFsdWVzX3RvID0gInZhbHVlIikgJT4lCiAgbXV0YXRlKG1ldHJpYyA9IGFzLmNoYXJhY3RlcihtZXRyaWMpLAogICAgICAgICBtZXRyaWMgPSByZWNvZGUobWV0cmljLAogICAgICAgICAgICAgICAgICAgICAgICAgIm5fcGF0Y2giID0gIk51bWJlciBvZiBQYXRjaGVzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICJwYXRjaF9kZW4iID0gIlBhdGNoIERlbnNpdHkiLAogICAgICAgICAgICAgICAgICAgICAgICAgInRvdGFsX2FyZWEiID0gIlRvdGFsIGFyZWEgKGttMikiLAogICAgICAgICAgICAgICAgICAgICAgICAgInByb3BfYXJlYSIgPSAiUHJvcG9ydGlvbiBvZiBBcmVhIikpKQoKZGYkc291cmNlIDwtIGZhY3RvcihkZiRzb3VyY2UsIGxldmVscz1jKCdBc3BlbjEwbScsICdBc3BlbjMwbScsICdMRkVWVCcsICdUcmVlTWFwJywgJ0lUU1AnKSkKaGVhZChkZikKCihmOGIgPC0gZ2dwbG90KGRhdGE9ZGYsIGFlcyh5PXZhbHVlLCB4PWZhY3Rvcihzb3VyY2UpLCBmaWxsPWZhY3RvcihyZWdpb24pKSkgKwogIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IiwgcG9zaXRpb249ImRvZGdlIiwgd2lkdGg9MC43KSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscz1zY2FsZXM6OmxhYmVsX251bWJlcl9zaShhY2N1cmFjeSA9IDEpKSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWNvbHMpICsKICBsYWJzKHg9IiIsIHk9IlZhbHVlIiwgdGl0bGU9IkxhbmRzY2FwZSBNZXRyaWNzIiwgZmlsbD0iUmVnaW9uOiAiKSArCiAgdGhlbWUoYXhpcy50aXRsZS54ID0gTlVMTCkgKwogIGZhY2V0X3dyYXAofm1ldHJpYywgc2NhbGVzID0gImZyZWVfeSIpICsKICB0aGVtZV9saWdodCgxMikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSwgc2l6ZT0xMSksCiAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoYW5nbGUgPSAwLCB2anVzdD0wLjEsIHNpemU9MTEpLAogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChzaXplPTEyLCBmYWNlPSJpdGFsaWMiLCB2anVzdD0wLjUpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChzaXplPTEyLCBmYWNlPSJpdGFsaWMiLCB2anVzdD0tMC41KSwKICAgICAgICBwbG90Lm1hcmdpbiA9IHVuaXQoYygwLjUsMC41LDAuNSwwLjUpLCJjbSIpLAogICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiLAogICAgICAgIGxlZ2VuZC5zcGFjaW5nLnggPSB1bml0KDAuNSwiY20iKSwKICAgICAgICB0ZXh0ID0gZWxlbWVudF90ZXh0KGZhbWlseSA9ICJBcmlhbCIpKSkKCmdnc2F2ZShmOGIsIGZpbGUgPSAiLi4vLi4vZmlndXJlcy9GaWd1cmU4Ql9sYW5kc2NhcGVfbWV0cmljcy5wbmciLCBkcGkgPSAzMDApICMgYWRqdXN0IGRwaSBhY2NvcmRpbmdseQoKYGBgCgpgYGB7ciBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9MTB9CihhcnIgPC0gZ2dhcnJhbmdlKGY4YixmOGEsbnJvdz0yLG5jb2w9MSxsYWJlbHM9YygiQSIsIkIiKSwgY29tbW9uLmxlZ2VuZCA9IFQpKQpnZ3NhdmUoYXJyLCBmaWxlPSIuLi8uLi9maWd1cmVzL0ZpZ3VyZThfTGFuZHNjYXBlX1BhdGNoX01ldHJpY3MucG5nIiwgZHBpPTMwMCkKYGBgCgpUaGUgMTAtbSBhc3BlbiBjbGFzc2lmaWNhdGlvbiBpZGVudGlmaWVkIFh4IG1vcmUgcGF0Y2ggdGhhbiByZWZlcmVuY2UgaW1hZ2VzOgoKYGBge3J9CgpwcmludCgiJSBEaWZmZXJlbmNlIGluIG51bWJlciBvZiBwYXRjaGVzIGFjcm9zcyB0aGUgU291dGhlcm4gUm9ja2llczogIikKYSA8LSBkZiAlPiUgZmlsdGVyKHJlZ2lvbj09IlNSTUUiLG1ldHJpYz09Ik51bWJlciBvZiBQYXRjaGVzIixzb3VyY2U9PSJBc3BlbjEwbSIpCmIgPC0gZGYgJT4lIGZpbHRlcihyZWdpb249PSJTUk1FIixtZXRyaWM9PSJOdW1iZXIgb2YgUGF0Y2hlcyIsc291cmNlPT0iTEZFVlQiKQooKGIkdmFsdWUtYSR2YWx1ZSkvYiR2YWx1ZSkqMTAwCgpwcmludCgiJSBEaWZmZXJlbmNlIGluIG51bWJlciBvZiBwYXRjaGVzIGFjcm9zcyB0aGUgV2hpdGUgUml2ZXIgTkY6ICIpCmEgPC0gZGYgJT4lIGZpbHRlcihyZWdpb249PSJXUk5GIixtZXRyaWM9PSJOdW1iZXIgb2YgUGF0Y2hlcyIsc291cmNlPT0iQXNwZW4xMG0iKQpiIDwtIGRmICU+JSBmaWx0ZXIocmVnaW9uPT0iV1JORiIsbWV0cmljPT0iTnVtYmVyIG9mIFBhdGNoZXMiLHNvdXJjZT09IkxGRVZUIikKKChiJHZhbHVlLWEkdmFsdWUpL2IkdmFsdWUpKjEwMAoKcHJpbnQoIn5+fn5+fn5+fn5+fn4iKQoKcHJpbnQoIn5+fn5+fn5+fn5+fn4iKQoKcHJpbnQoIiUgRGlmZmVyZW5jZSBpbiBwYXRjaCBkZW5zaXR5OiAiKQphIDwtIGRmICU+JSBmaWx0ZXIobWV0cmljPT0iUGF0Y2ggRGVuc2l0eSIsc291cmNlPT0iQXNwZW4xMG0iKQpiIDwtIGRmICU+JSBmaWx0ZXIobWV0cmljPT0iUGF0Y2ggRGVuc2l0eSIsc291cmNlPT0iTEZFVlQiKQooKGIkdmFsdWUtYSR2YWx1ZSkvYiR2YWx1ZSkqMTAwCgpwcmludCgiJSBEaWZmZXJlbmNlIGluIHRvdGFsIGFyZWE6IikKYSA8LSBkZiAlPiUgZmlsdGVyKG1ldHJpYz09IlRvdGFsIGFyZWEgKGttMikiLHNvdXJjZT09IkFzcGVuMTBtIikKYWEgPC0gZGYgJT4lIGZpbHRlcihtZXRyaWM9PSJUb3RhbCBhcmVhIChrbTIpIixzb3VyY2U9PSJBc3BlbjMwbSIpCmIgPC0gZGYgJT4lIGZpbHRlcihtZXRyaWM9PSJUb3RhbCBhcmVhIChrbTIpIixzb3VyY2U9PSJMRkVWVCIpCigoYiR2YWx1ZS1hJHZhbHVlKS9iJHZhbHVlKSoxMDAKKChiJHZhbHVlLWFhJHZhbHVlKS9iJHZhbHVlKSoxMDAKCnJtKGEsYWEsYikKCmBgYAoKCiMgU3VwcGxlbWVudGFsCgpGaWd1cmUgUzE6IFF1YWtpbmcgQXNwZW4gUGhlbm9sb2d5CgpMb2FkIHRoZSBzcGF0aWFsIGJsb2NrIGdyaWQgYW5kIHBoZW5vbG9neSB0aW1lLXNlcmllcyBzdW1tYXJpZXMgYnkgc3BhdGlhbCBibG9jayBmb3IgdGhlIFNvdXRoZXJuIFJvY2tpZXMuIEpvaW4gdGhlIHBoZW5vbG9neSBzdW1tYXJpZXMgdG8gdGhlaXIgZ2VvbWV0cmllcy4KCmBgYHtyfQojIExvYWQgdGhlIGdyaWRzLCBrZWVwIHRoZSBlbGV2YXRpb24gYXR0cmlidXRlIGFuZCBUcmVlTWFwIHN1bSAoYXNwZW4gYXJlYSkKZ3JpZCA8LSBzdF9yZWFkKCcuLi8uLi9kYXRhL3NwYXRpYWwvbW9kL2JvdW5kYXJpZXMvc3BhdGlhbF9ibG9ja19ncmlkXzUwa20yLmdwa2cnLAogICAgICAgICAgICAgICAgcXVpZXQ9VCkgJT4lCiAgc2VsZWN0KGdyaWRfaWQsZWxldmF0aW9uX21uLHRyZWVtYXBfc3VtLHRyZWVtYXBfcGN0KQoKIyBMb2FkIHRoZSBwaGVub2xvZ3kgYnkgc3BhdGlhbCBibG9jayBncmlkCnBoZW5vbG9neSA8LSByZWFkX2NzdignLi4vLi4vZGF0YS90YWJ1bGFyL21vZC9waGVub2xvZ3kvdmlpcnNfcGhlbm9sb2d5X2J5X2dyaWRfaW5fYXNwZW4uY3N2JywKICAgICAgICAgICAgICAgICAgICAgIHNob3dfY29sX3R5cGVzID0gRkFMU0UpICU+JQogIHJlbmFtZShmaWQgPSBgc3lzdGVtOmluZGV4YCkgJT4lCiAgbXV0YXRlKGdyaWRfaWQgPSBzdHJfc3ViKGZpZCwgLTcpLAogICAgICAgICB5ZWFyID0gYXMuaW50ZWdlcihzdHJfc3ViKGZpZCwgMSwgNCkpKSAlPiUKICBzZWxlY3QoLWMoZmlkLGxhYmVsLGNvdW50LC5nZW8pKSAlPiUKICAjIENyZWF0ZSBET1kgdmVyc2lvbnMgb2YgdGhlIHBoZW5vbG9neSBtZXRyaWNzIGZvciBwbG90dGluZyBhbmQgc3RhdGlzdGljYWwgYW5hbHlzaXMKICBtdXRhdGUoCiAgICBzZWFzb25fbGVuZ3RoID0gR3Jvd2luZ19TZWFzb25fTGVuZ3RoXzFfbWVkaWFuLAogICAgZG95X21pZGdyZWVudXAgPSB5ZGF5KERhdGVfTWlkX0dyZWVudXBfUGhhc2VfMV9tZWRpYW4pLAogICAgZG95X21pZHNlbmVzY2VuY2UgPSB5ZGF5KERhdGVfTWlkX1NlbmVzY2VuY2VfUGhhc2VfMV9tZWRpYW4pLAogICAgZG95X2dyZWVuX2RlYyA9IHlkYXkoT25zZXRfR3JlZW5uZXNzX0RlY3JlYXNlXzFfbWVkaWFuKSwKICAgIGRveV9ncmVlbl9pbmMgPSB5ZGF5KE9uc2V0X0dyZWVubmVzc19JbmNyZWFzZV8xX21lZGlhbiksCiAgICBkb3lfZ3JlZW5fbWF4ID0geWRheShPbnNldF9HcmVlbm5lc3NfTWF4aW11bV8xX21lZGlhbiksCiAgICBkb3lfZ3JlZW5fbWluID0geWRheShPbnNldF9HcmVlbm5lc3NfTWluaW11bV8xX21lZGlhbikKICApICU+JQogIGxlZnRfam9pbihncmlkJT4lYXNfdGliYmxlKCksIGJ5PSJncmlkX2lkIikgJT4lCiAgc2VsZWN0KGdyaWRfaWQseWVhcixlbGV2YXRpb25fbW4sc2Vhc29uX2xlbmd0aCxkb3lfbWlkZ3JlZW51cCxkb3lfbWlkc2VuZXNjZW5jZSwKICAgICAgICAgZG95X2dyZWVuX2RlYyxkb3lfZ3JlZW5faW5jLGRveV9ncmVlbl9tYXgsZG95X2dyZWVuX21pbikKCmdsaW1wc2UocGhlbm9sb2d5KQpgYGAKClNwYXRpYWwgbWFwcyBvZiB0aGUga2V5IHBoZW5vbG9neSBtZXRyaWNzLgoKYGBge3IgZmlnLndpZHRoPTQsIGZpZy5oZWlnaHQ9NX0KCiMgQ2FsY3VsYXRlIHRoZSBtZWRpYW4gcGhlbm9sb2d5IG1ldHJpY3MKcGhlbm9sb2d5Lm1kIDwtIHBoZW5vbG9neSAlPiUKICBncm91cF9ieShncmlkX2lkKSAlPiUKICBzdW1tYXJpc2UoYWNyb3NzKHN0YXJ0c193aXRoKCJkb3kiKSwgbWVkaWFuLCBuYS5ybT1UKSkKCiMgSm9pbiB0byB0aGUgc3BhdGlhbCBkYXRhIGFuZCBwaXZvdCBsb25nZXIKZ3JpZC5sIDwtIGdyaWQgJT4lIAogIGxlZnRfam9pbihwaGVub2xvZ3kubWQsIGJ5PSJncmlkX2lkIikgJT4lCiAgcGl2b3RfbG9uZ2VyKGNvbHMgPSBzdGFydHNfd2l0aCgiZG95IiksIG5hbWVzX3RvID0gIm1ldHJpYyIsIHZhbHVlc190byA9ICJ2YWx1ZSIpICU+JQogIGZpbHRlcih0cmVlbWFwX3BjdCA+IDAuMDUpICMgS2VlcCBvbmx5IGJsb2NrcyB3aXRoIHNvbWUgYXNwZW4gY292ZXIKCm9yZGVyZWRfbWV0cmljcyA8LSBjKAogICJkb3lfZ3JlZW5faW5jIiwiZG95X21pZGdyZWVudXAiLCJkb3lfZ3JlZW5fbWF4IiwKICAiZG95X2dyZWVuX2RlYyIsImRveV9taWRzZW5lc2NlbmNlIiwiZG95X2dyZWVuX21pbiIpIAoKIyBUaWR5IHRoZSBuYW1lcyBmb3IgcGxvdHRpbmcKbWV0cmljX25hbWVzIDwtIGMoCiAgZG95X2dyZWVuX2luYyA9ICJPbnNldCBHcmVlbm5lc3MgSW5jcmVhc2UiLAogIGRveV9taWRncmVlbnVwID0gIk1pZCBHcmVlbnVwIFBoYXNlIiwKICBkb3lfZ3JlZW5fbWF4ID0gIk9uc2V0IEdyZWVubmVzcyBNYXhpbXVtIiwKICBkb3lfZ3JlZW5fZGVjID0gIk9uc2V0IEdyZWVubmVzcyBEZWNyZWFzZSIsCiAgZG95X21pZHNlbmVzY2VuY2UgPSAiTWlkIFNlbmVzY2VuY2UgUGhhc2UiLAogIGRveV9ncmVlbl9taW4gPSAiT25zZXQgR3JlZW5uZXNzIE1pbmltdW0iCikKCiMgRml4IHRoZSBmYWN0b3IgbGV2ZWxzIGFuZCBsYWJlbHMKZ3JpZC5sJG1ldHJpYyA8LSBmYWN0b3IoZ3JpZC5sJG1ldHJpYywgbGV2ZWxzID0gb3JkZXJlZF9tZXRyaWNzLCBsYWJlbHMgPSBtZXRyaWNfbmFtZXMpCgpoZWFkKGdyaWQubCkKYGBgCgpgYGB7ciBmaWcud2lkdGg9Mi40LCBmaWcuaGVpZ2h0PTV9CgptZXRyaWNzIDwtIHVuaXF1ZShncmlkLmwkbWV0cmljKQoKcGxvdHMgPC0gbGlzdCgpCgpmb3IgKG0gaW4gbWV0cmljcyl7CiAgIyBGaWx0ZXIgZm9yIHRoZSBtZXRyaWMKICBnZGYgPC0gZ3JpZC5sICU+JSBmaWx0ZXIobWV0cmljID09IG0pCiAgCiAgIyBNYXAKICBwIDwtIGdncGxvdCgpICsKICAgIGdlb21fc2YoZGF0YT1ncmlkLCBmaWxsPU5BKSArCiAgICBnZW9tX3NmKGRhdGE9Z2RmLCBhZXMoZmlsbD12YWx1ZSkpICsKICAgIHNjYWxlX2ZpbGxfY29udGludW91cyhsb3cgPSAibGlnaHRncmVlbiIsIGhpZ2ggPSAiZGFya2dyZWVuIiwKICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBmdW5jdGlvbih4KSBmb3JtYXQoYXMuRGF0ZShhcy5udW1lcmljKHgpLCBvcmlnaW49IjE5NzAtMDEtMDEiKSwgIiViICVkIikpICsKICAgIGdlb21fc2YoZGF0YT1zcm1lLGZpbGw9TkEsY29sb3I9ImJsYWNrIixzaXplPTIuNSkgKwogICAgbGFicyhmaWxsPSIiLCB0aXRsZT1tKSArCiAgICB0aGVtZV92b2lkKDEyKSArCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiLAogICAgICAgICAgbGVnZW5kLnRleHQgPSBlbGVtZW50X3RleHQoYW5nbGUgPSAwLCBzaXplPTkpLCAgICMgRW5zdXJlIGxhYmVscyBhcmUgbm90IHJvdGF0ZWQKICAgICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIHNpemU9MTApLAogICAgICAgICAgcGxvdC5tYXJnaW4gPSBnZ3Bsb3QyOjptYXJnaW4oMC4xLCA1LCAwLjEsIDUsICJtbSIpCiAgICApICsgCiAgICBndWlkZXMoZmlsbCA9IGd1aWRlX2NvbG91cmJhcigKICAgICAgYmFyd2lkdGggPSAwLjUsIAogICAgICBiYXJoZWlnaHQgPSA2LCAKICAgICAgdGlja3MgPSBGQUxTRSwKICAgICAgbGFiZWwucG9zaXRpb24gPSAicmlnaHQiLCAKICAgICAgbGFiZWwudGhlbWUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDgpKQogICAgKQogICMgU2F2ZSB0aGUgaW5kaXZpZHVhbCBtYXAKICBnZ3NhdmUocCwgZmlsZT1wYXN0ZTAoIi4uLy4uL2ZpZ3VyZXMvRmlndXJlUzFCX1BoZW5vbG9neV9ieV9FbGV2YXRpb24iLG0sIi5wbmciKSwgZHBpPTMwMCkKICBwcmludChwKQogIHBsb3RzW1ttXV0gPC0gcAp9CgpgYGAKCkNyZWF0ZSBhIGNvbWJpbmVkIHBsb3QgZm9yIGZvdXIgbWV0cmljcy4KCmBgYHtyIGZpZy53aWR0aD02Ljc1LCBmaWcuaGVpZ2h0PTYuNX0KCmZzMWEgPC0gZ2dhcnJhbmdlKHBsb3RzW1sxXV0sIHBsb3RzW1szXV0sIHBsb3RzW1syXV0sIHBsb3RzW1s2XV0sCiAgICAgICAgICAgICAgICAgIG5jb2wgPSAyLCBucm93ID0gMiwgd2lkdGhzPWMoMSwxLDEsMSksIGhlaWdodHM9YygxLDEsMSwxKSwKICAgICAgICAgICAgICAgICAgYWxpZ24gPSAiaHYiLCBsYWJlbHMgPSBjKCJBIiwiQiIsIkMiLCJEIikpCmZzMWEKZ2dzYXZlKGZzMWEsIGZpbGU9Ii4uLy4uL2ZpZ3VyZXMvRmlndXJlUzFBX1BoZW5vbG9neU1hcHMucG5nIiwgZHBpPTMwMCkKCmBgYAoKClBsb3QgdGhlIHJlbGF0aW9uc2hpcCB3aXRoIGVsZXZhdGlvbiBhY3Jvc3MgbWV0cmljcy4KCmBgYHtyIG1lc3NhZ2U9Riwgd2FybmluZz1GfQoKbWV0cmljcyA8LSBjKCJNaWQgR3JlZW51cCBQaGFzZSIsICJPbnNldCBHcmVlbm5lc3MgRGVjcmVhc2UiLCAiTWlkIFNlbmVzY2VuY2UgUGhhc2UiLCAiT25zZXQgR3JlZW5uZXNzIE1pbmltdW0iKQoKZGYgPC0gZ3JpZC5sICU+JSBmaWx0ZXIobWV0cmljICVpbiUgbWV0cmljcykKCiMgSW5kaXZpZHVhbCBwbG90cwpmb3IgKG0gaW4gbWV0cmljcyl7CiAgIyBGaWx0ZXIgZm9yIHRoZSBtZXRyaWMKICBnZGYgPC0gZ3JpZC5sICU+JSBmaWx0ZXIobWV0cmljID09IG0pCiAgCiAgIyBNYXAKICBwIDwtIGdncGxvdChkYXRhPWdkZiwgYWVzKHg9dmFsdWUseT1lbGV2YXRpb25fbW4pKSArCiAgICBnZW9tX3Ntb290aChtZXRob2Q9ImxtIixjb2xvdXI9ImdyYXk0MCIsIGZpbGw9ImdyYXk3MCIsIHNpemU9MC41KSArCiAgICBnZW9tX3BvaW50KHNpemU9MC40KSArCiAgICB0aGVtZV9idyg3KSArCiAgICB0aGVtZShheGlzLnRleHQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9NiksCiAgICAgICAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpKSArCiAgICBsYWJzKHg9IkRheSBvZiBZZWFyIix5PSJFbGV2YXRpb24iKQogIGdnc2F2ZShwLCBmaWxlPXBhc3RlMCgiLi4vLi4vZmlndXJlcy9GaWd1cmVTMUJfUGhlbm9sb2d5X2J5X0VsZXZhdGlvbl8iLG0sIi5wbmciKSwgd2lkdGg9MS41LCBoZWlnaHQ9MS41KQogIHByaW50KHApCn0KCiMgRmFjZXQgcGxvdAooZnMxYiA8LSBnZ3Bsb3QoZGF0YT1kZiwgYWVzKHg9dmFsdWUseT1lbGV2YXRpb25fbW4pKSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kPSJsbSIsY29sb3VyPSJncmF5NDAiLCBmaWxsPSJncmF5NzAiLCBzaXplPTAuOCkgKwogIGdlb21fcG9pbnQoc2l6ZT0wLjgpICsKICBmYWNldF93cmFwKH5tZXRyaWMsIHNjYWxlcz0iZnJlZV94IiwgbnJvdz0yKSArCiAgdGhlbWVfbWluaW1hbCgxMikgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT0xMCksCiAgICAgICAgYXhpcy50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemU9OCksCiAgICAgICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplPTEwKSwKICAgICAgICBzdHJpcC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemU9OCksCiAgICAgICAgcGFuZWwuc3BhY2luZyA9IHVuaXQoMi41LCAibW0iKSkgKwogIGxhYnMoeD0iRGF5IG9mIFllYXIiLHk9IkVsZXZhdGlvbiIsIHRhZz0iQiIpKQoKIyBTYXZlIG91dApnZ3NhdmUoZnMxYiwgZmlsZT0iLi4vLi4vZmlndXJlcy9GaWd1cmVTMUJfUGhlbm9sb2d5X2J5X0VsZXZhdGlvbi5wbmciLCBkcGk9MzAwKQoKcm0obWV0cmljcyxkZikKCmBgYAoKTWVyZ2UgdGhlIHR3byBwbG90cy4KCmBgYHtyIGZpZy53aWR0aD00LjUsIGZpZy5oZWlnaHQ9NX0KKGZzMSA8LSBnZ2FycmFuZ2UoZnMxYSwgZnMxYiwgbnJvdz0yLCBuY29sPTEsIGFsaWduPSJ2IikpCmdnc2F2ZShmczEsIGZpbGU9Ii4uLy4uL2ZpZ3VyZXMvRmlndXJlUzFfUGhlbm9sb2d5LnBuZyIsIGRwaT0zMDApCmBgYAoKYGBge3J9CiMgUmVzaGFwZSBmcm9tIHdpZGUgdG8gbG9uZyBmb3JtYXQKcGhlbm9sb2d5LmwgPC0gcGhlbm9sb2d5ICU+JQogIHBpdm90X2xvbmdlcigKICAgIGNvbHMgPSBjKGRveV9taWRncmVlbnVwLGRveV9taWRzZW5lc2NlbmNlLGRveV9ncmVlbl9kZWMsCiAgICAgICAgICAgICBkb3lfZ3JlZW5faW5jLGRveV9ncmVlbl9tYXgsZG95X2dyZWVuX21pbiksCiAgICBuYW1lc190byA9ICJtZXRyaWMiLAogICAgdmFsdWVzX3RvID0gImRveSIKICApCgpvcmRlcmVkX21ldHJpY3MgPC0gYygKICAiZG95X2dyZWVuX2luYyIsImRveV9taWRncmVlbnVwIiwiZG95X2dyZWVuX21heCIsCiAgImRveV9ncmVlbl9kZWMiLCJkb3lfbWlkc2VuZXNjZW5jZSIsImRveV9ncmVlbl9taW4iKSAKCm1ldHJpY19uYW1lcyA8LSBjKAogIGRveV9ncmVlbl9pbmMgPSAiT25zZXQgR3JlZW5uZXNzIEluY3JlYXNlIiwKICBkb3lfbWlkZ3JlZW51cCA9ICJNaWQgR3JlZW51cCBQaGFzZSIsCiAgZG95X2dyZWVuX21heCA9ICJPbnNldCBHcmVlbm5lc3MgTWF4aW11bSIsCiAgZG95X2dyZWVuX2RlYyA9ICJPbnNldCBHcmVlbm5lc3MgRGVjcmVhc2UiLAogIGRveV9taWRzZW5lc2NlbmNlID0gIk1pZCBTZW5lc2NlbmNlIFBoYXNlIiwKICBkb3lfZ3JlZW5fbWluID0gIk9uc2V0IEdyZWVubmVzcyBNaW5pbXVtIgopCgpwaGVub2xvZ3kubCRtZXRyaWMgPC0gZmFjdG9yKAogIHBoZW5vbG9neS5sJG1ldHJpYywgCiAgbGV2ZWxzID0gb3JkZXJlZF9tZXRyaWNzLCAKICBsYWJlbHMgPSBtZXRyaWNfbmFtZXMKKQoKIyBDcmVhdGUgdGhlIHBsb3Qgd2l0aCBmYWNldF93cmFwCmdncGxvdChwaGVub2xvZ3kubCwgYWVzKHg9ZmFjdG9yKHllYXIpLCB5PWRveSkpICsKICBnZW9tX2JveHBsb3QoKSArCiAgZmFjZXRfd3JhcCh+IG1ldHJpYywgc2NhbGVzID0gImZpeGVkIikgKwogIHRoZW1lX21pbmltYWwoKSArCiAgbGFicyh0aXRsZSA9ICJET1kgUGhlbm9sb2d5IE1ldHJpY3MgKDIwMTMtMjAyMikiLAogICAgICAgeCA9ICJZZWFyIiwKICAgICAgIHkgPSAiRGF5IG9mIFllYXIiLAogICAgICAgZmlsbCA9ICJZZWFyIikgKwogIHRoZW1lX2xpZ2h0KCkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpCgojIFN0YWNrZWQgYm94IHBsb3RzCmdncGxvdChwaGVub2xvZ3kubCwgYWVzKHg9ZmFjdG9yKHllYXIpLCB5PWRveSwgZmlsbD1tZXRyaWMpKSArCiAgZ2VvbV9ib3hwbG90KHdpZHRoPTEpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIGxhYnMoeCA9ICJZZWFyIiwKICAgICAgIHkgPSAiRGF5IG9mIFllYXIiLAogICAgICAgZmlsbCA9ICJNZXRyaWMiKSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSwKICAgICAgICBsZWdlbmQucG9zaXRpb24gPSAidG9wIikKYGBgCgpST0MsIEYxLCBNQ0M6CgpgYGB7cn0KCmFjY21lYXMgPC0gcmVhZF9jc3YoJy4uLy4uL2RhdGEvdGFidWxhci9tb2QvcmVzdWx0cy9hY2NtZWFzX3Byb3AuY3N2JykKCiMgQ2FsY3VsYXRlIHRoZSBhdmVyYWdlcyBmb3IgZWFjaCBjdXRvZmYgdmFsdWUgYWNyb3NzIG1vZGVsIHJ1bnMKCmFjY21lYXMubW4gPC0gYWNjbWVhcyAlPiUKICBtdXRhdGUoY3V0b2ZmID0gYXMuY2hhcmFjdGVyKGN1dG9mZikpICU+JQogIGdyb3VwX2J5KGN1dG9mZikgJT4lCiAgc3VtbWFyaXNlKAogICAgcHJNbiA9IG1lYW4ocHJTaXplLG5hLnJtPVQpLCAjIHNpemUgb2YgcHJlc2VuY2UgZGF0YSAobWVhbikKICAgIHByU2QgPSBzZChwclNpemUsbmEucm09VCksICMgc2l6ZSBvZiBwcmVzZW5jZSBkYXRhIChzZCkKICAgIGJnTW4gPSBtZWFuKGJnU2l6ZSxuYS5ybT1UKSwgIyBzaXplIG9mIGJhY2tncm91bmQgZGF0YSAobWVhbikKICAgIGJnU2QgPSBzZChiZ1NpemUsbmEucm09VCksICMgc2l6ZSBvZiBiYWNrZ3JvdW5kIGRhdGEgKHNkKQogICAgZnByTW4gPSBtZWFuKGZwcixuYS5ybT1UKSwgIyBGYWxzZSBQb3NpdGl2ZSBSYXRlIChtZWFuKQogICAgZnByU2QgPSBzZChmcHIsbmEucm09VCksCiAgICB0cHJNbiA9IG1lYW4odHByLG5hLnJtPVQpLCAjIFRydWUgUG9zaXRpdmUgUmF0ZSAobWVhbikKICAgIHRwclNkID0gc2QodHByLG5hLnJtPVQpLAogICAgcHJlY2lzaW9uID0gbWVhbihwcmVjaXNpb24sbmEucm09VCksICMgcHJlY2lzaW9uIChtZWFuKQogICAgcHJlY2lzaW9uU2QgPSBzZChwcmVjaXNpb24sbmEucm09VCksIAogICAgcmVjYWxsID0gbWVhbihyZWNhbGwsbmEucm09VCksICMgcmVjYWxsIChtZWFuKQogICAgcmVjYWxsU2QgPSBzZChyZWNhbGwsbmEucm09VCksCiAgICBmMSA9IG1lYW4oZjEsbmEucm09VCksICMgRjEgc2NvcmUgKG1lYW4pCiAgICBmMVNkID0gc2QoZjEsbmEucm09VCksCiAgICBnbWVhbiA9IG1lYW4oZ21lYW4sbmEucm09VCksICMgR2VvbWV0cmljIE1lYW4gKG1lYW4pCiAgICBnbWVhblNkID0gc2QoZ21lYW4sbmEucm09VCksCiAgICBtY2MgPSBtZWFuKG1jYyxuYS5ybT1UKSwgIyBNYXR0aGV3J3MgQ29ycmVsYXRpb24gQ29lZmZpY2llbnQgKG1lYW4pCiAgICBtY2NTZCA9IHNkKG1jYyxuYS5ybT1UKSwKICAgIGFjY3VyYWN5ID0gbWVhbihhY2N1cmFjeSxuYS5ybT1UKSwgIyBPdmVyYWxsIGFjY3VyYWN5IChtZWFuKQogICAgYWNjdXJhY3lTZCA9IHNkKGFjY3VyYWN5LG5hLnJtPVQpCiAgKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgbXV0YXRlKGN1dG9mZiA9IHJvdW5kKGFzLmRvdWJsZShjdXRvZmYpLDMpKQoKIyBDYWxjdWxhdGUgb3B0aW11bSB0aHJlc2hvbGQgYmFzZWQgb24gRjEgc3RhdGlzdGljCgpjdXRvZmZPcHRNbiA9IGFjY21lYXMubW5bd2hpY2gubWF4KGFjY21lYXMubW4kZjEpLF0kY3V0b2ZmCnByZWNpc2lvbk9wdEYxTW4gPSBhY2NtZWFzLm1uW3doaWNoLm1heChhY2NtZWFzLm1uJGYxKSxdJHByZWNpc2lvbgpyZWNhbGxPcHRGMU1uID0gYWNjbWVhcy5tblt3aGljaC5tYXgoYWNjbWVhcy5tbiRmMSksXSRyZWNhbGwKZnByT3B0TW4gPSBhY2NtZWFzLm1uW3doaWNoLm1heChhY2NtZWFzLm1uJGYxKSxdJGZwck1uCnRwck9wdE1uID0gYWNjbWVhcy5tblt3aGljaC5tYXgoYWNjbWVhcy5tbiRmMSksXSR0cHJNbgptYXhfZjEgPC0gbWF4KGFjY21lYXMubW4kZjEsIG5hLnJtID0gVFJVRSkKCmBgYAoKCmBgYHtyIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQoKIyBST0MgQ3VydmUgYW5kIGxhYmVsIHRoZSBBVUMgdmFsdWUgKGFwcHJveGltYXRpb24pCgojIEdldCB0aGUgQVVDIGFwcHJveAoKIyBFbnN1cmUgZGF0YSBpcyBzb3J0ZWQgYnkgRlBSCmFjY21lYXNfbW5fc29ydGVkIDwtIGFjY21lYXMubW5bb3JkZXIoYWNjbWVhcy5tbiRmcHJNbiksIF0KIyBDb21wdXRlIEFVQyB1c2luZyB0cmFweiBmdW5jdGlvbiBpbiAncHJhY21hJwphdWNfYXZnX2FwcHJveCA8LSB0cmFweihhY2NtZWFzX21uX3NvcnRlZCRmcHJNbiwgYWNjbWVhc19tbl9zb3J0ZWQkdHByTW4pCgpmczJhIDwtIGdncGxvdChkYXRhPWFjY21lYXMpICsKICBnZW9tX2xpbmUoYWVzKHg9ZnByLHk9dHByLGNvbG9yPWZhY3Rvcihtb2RlbCkpLGxpbmV3aWR0aD0wLjQpICsKICBzY2FsZV9jb2xvcl92aXJpZGlzX2Qob3B0aW9uPSJ0dXJibyIpICsKICBnZW9tX2xpbmUoZGF0YT1hY2NtZWFzLm1uLCBhZXMoeCA9IGZwck1uLCB5ID0gdHByTW4pLCBjb2xvciA9ICJibGFjayIsIHNpemUgPSAwLjgpICsgICMgUk9DIGN1cnZlIGF2ZXJhZ2UKICBzY2FsZV94X2NvbnRpbnVvdXMobGltaXRzPWMoMCwxKSkgKyAgIyBTZXQgeCBheGlzIGxpbWl0cyBmcm9tIDAgdG8gMQogIHNjYWxlX3lfY29udGludW91cyhsaW1pdHM9YygwLDEpKSArCiAgbGFicyh4PSdGYWxzZSBQb3NpdGl2ZSBSYXRlJywgeT0nVHJ1ZSBQb3NpdGl2ZSBSYXRlJywgdGFnPSJBIikgKwogIGdlb21fcG9pbnQoYWVzKHggPSBmcHJPcHRNbiwgeSA9IHRwck9wdE1uKSwKICAgICAgICAgICAgIGNvbG9yID0gInJlZCIsIHNpemUgPSAzLCBzaGFwZSA9IDE5KSArICAjIG9wdGltYWwgdGhyZXNob2xkIHBvaW50CiAgZ2VvbV90ZXh0KGFlcyh4PWZwck9wdE1uLCB5PXRwck9wdE1uKSwKICAgICAgICAgICAgbGFiZWwgPSBwYXN0ZTAoJ09wdGltYWwgdGhyZXNob2xkOiAnLCByb3VuZChjdXRvZmZPcHRNbiwzKSwnXG5BcHByb3guIEFVQzogJywgcm91bmQoYXVjX2F2Z19hcHByb3gsMykpLAogICAgICAgICAgICBudWRnZV94ID0gMC4zOCwgbnVkZ2VfeSA9IC0wLjA4LCBzaXplID0gMy41KSArCiAgY29vcmRfZml4ZWQocmF0aW8gPSAxKSArICAjIHRvIGtlZXAgdGhlIHggYW5kIHkgYXhlcyBzY2FsZXMgc2FtZQogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDAsIHNsb3BlID0gMSwgbGluZXR5cGUgPSAiZGFzaGVkIiwgY29sb3IgPSAiYmxhY2siKSArICAjIGRpYWdvbmFsCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLAogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChzaXplPTExLGZhY2U9Iml0YWxpYyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1hcmdpbiA9IHVuaXQoYygwLDAuNCwwLDApLCAiY20iKSksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF90ZXh0KHNpemU9MTEsZmFjZT0iaXRhbGljIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWFyZ2luID0gdW5pdChjKDAuNCwwLDAsMCksICJjbSIpKSwKICAgICAgICBheGlzLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT0xMCksCiAgICAgICAgc3RyaXAudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMSkpCgpmczJiIDwtIGdncGxvdChkYXRhPWFjY21lYXMpICsKICBnZW9tX2xpbmUoYWVzKHg9Y3V0b2ZmLHk9ZjEsY29sb3I9ZmFjdG9yKG1vZGVsKSksbGluZXdpZHRoPTAuNCkgKwogIHNjYWxlX2NvbG9yX3ZpcmlkaXNfZChvcHRpb249InR1cmJvIikgKwogIGdlb21fbGluZShkYXRhPWFjY21lYXMubW4sIGFlcyh4PWN1dG9mZiwgeSA9IGYxKSwgY29sb3IgPSAiYmxhY2siLCBzaXplID0gMC44KSArICAjIFJPQyBjdXJ2ZSBhdmVyYWdlCiAgIyBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9MC40MjQsIGxpbmV0eXBlPSJkYXNoZWQiLCBjb2xvcj0iYmxhY2siKSArCiAgZ2VvbV9wb2ludChhZXMoeD1jdXRvZmZPcHRNbiwgeT1tYXhfZjEpLAogICAgICAgICAgICAgY29sb3IgPSAicmVkIiwgc2l6ZSA9IDMsIHNoYXBlID0gMTkpICsKICBnZW9tX3RleHQoYWVzKHg9Y3V0b2ZmT3B0TW4sIHk9bWF4X2YxKSwKICAgICAgICAgICAgbGFiZWwgPSBwYXN0ZTAoJ09wdGltYWwgdGhyZXNob2xkOiAnLCByb3VuZChjdXRvZmZPcHRNbiwzKSksCiAgICAgICAgICAgIG51ZGdlX3ggPSAwLjAsIG51ZGdlX3kgPSAwLjA4LCBzaXplID0gMy41KSArCiAgc2NhbGVfeF9jb250aW51b3VzKGxpbWl0cz1jKDAsMSkpICsgICMgU2V0IHggYXhpcyBsaW1pdHMgZnJvbSAwIHRvIDEKICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzPWMoMCwxKSkgKwogIGxhYnMoeD0nVGhyZXNob2xkJywgeT0nRjEgU2NvcmUnLCB0YWc9IkIiKSArCiAgY29vcmRfZml4ZWQocmF0aW8gPSAxKSArICAjIHRvIGtlZXAgdGhlIHggYW5kIHkgYXhlcyBzY2FsZXMgc2FtZQogIHRoZW1lX2xpZ2h0KCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoc2l6ZT0xMSxmYWNlPSJpdGFsaWMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXJnaW4gPSB1bml0KGMoMCwwLjQsMCwwKSwgImNtIikpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChzaXplPTExLGZhY2U9Iml0YWxpYyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1hcmdpbiA9IHVuaXQoYygwLjQsMCwwLDApLCAiY20iKSksCiAgICAgICAgYXhpcy50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemU9MTApLAogICAgICAgIHN0cmlwLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTEpKQoKKGZzMiA8LSBnZ2FycmFuZ2UoZnMyYSxmczJiKSkKCmdnc2F2ZShmczIsIGZpbGU9Ii4uLy4uL2ZpZ3VyZXMvRmlndXJlUzJfQVVDX0YxTWF4LnBuZyIsIGRwaT0zMDApCgpybShmczIsZnMyYSxmczJiLGFjY21lYXNfbW5fc29ydGVkLGF1Y19hdmdfYXBwcm94KQoKYGBgCgpUYWJsZSBTMTogUGVyZm9ybWFuY2UgbWV0cmljcyBmb3IgdGhlIG9wdGltdW0gY3V0b2ZmIHZhbHVlCgpDcmVhdGUgYSBwcmV0dHkgdGFibGUuCgpgYGB7cn0KCmxpYnJhcnkoZmxleHRhYmxlLCBxdWlldGx5ID0gVCkKCiMgR2V0IHRoZSBiZXN0IEYxIHNjb3JlIGZvciBlYWNoIG1vZGVsIHJ1bgphY2NtZWFzLmJlc3QgPC0gYWNjbWVhcyAlPiUgCiAgbXV0YXRlKG1vZGVsID0gYXMuZmFjdG9yKG1vZGVsKSkgJT4lCiAgZ3JvdXBfYnkobW9kZWwpICU+JSAKICB0b3BfbigxLGYxKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgbXV0YXRlKG1vZGVsX2l0ZXIgPSByb3dfbnVtYmVyKCkpICU+JQogIHNlbGVjdChtb2RlbCxwclNpemUsYmdTaXplLHRwcixwcmVjaXNpb24scmVjYWxsLGYxLGN1dG9mZikKCmhlYWQoYWNjbWVhcy5iZXN0LDEwKQoKIyBGaXggbmFtZXMgYW5kIGFkZCB1bml0cwpuYW1lcyhhY2NtZWFzLmJlc3QpIDwtIGMoIk1vZGVsIFNlZWQiLAogICAgICAgICAgICAgICAgICAgICAgICAgIiMgb2YgUHJlc2VuY2UiLAogICAgICAgICAgICAgICAgICAgICAgICAgIiMgb2YgQmFja2dyb3VuZCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAiVHJ1ZSBQb3NpdGl2ZSBSYXRlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICJQcmVjaXNpb24iLAogICAgICAgICAgICAgICAgICAgICAgICAgIlJlY2FsbCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAiRjEtU2NvcmUiLAogICAgICAgICAgICAgICAgICAgICAgICAgIlRocmVzaG9sZCIpCgojIFNldCBmb250IG5hbWUgZm9yIHRhYmxlCmZvbnRuYW1lIDwtICJUaW1lcyBOZXcgUm9tYW4iCgojIENyZWF0ZSB0aGUgZmxleHRhYmxlCihmdDEgPC0gZmxleHRhYmxlKGFjY21lYXMuYmVzdCkgJT4lCiBmb250KGZvbnRuYW1lID0gZm9udG5hbWUsIHBhcnQgPSAiYWxsIikgJT4lCiBhdXRvZml0KCkgJT4lIGZpdF90b193aWR0aCg2LjUpKQpwcmludChmdDEsIHByZXZpZXcgPSAiZG9jeCIpCgojIFdyaXRlIHRvIGEgQ1NWCndyaXRlX2NzdihhY2NtZWFzLmJlc3QsICIuLi8uLi9maWd1cmVzL1RhYmxlUzFfQWNjdXJhY3lfTWF4RjEuY3N2IikKCmBgYAoKQ2xlYW4gdXAhCgpgYGB7cn0KCmBgYAo=